David H
David H

Reputation: 1625

Header-only library circular dependency

I'm trying to create a header-only C++ library around an external C API. The C API uses void * pointers as handles. Here's the idea:

// resource.hpp
class Resource {
public:
    // RAII constructor, destructor, etc.
    // ...
    void do_something(const Handle & h) {
        do_something_impl( (void *) h);
    }
};

// handle.hpp
class Handle
{
public:
    Handle(size_t n, const Resource & res)
        : p_(res.allocate(n)), res_(res) {}

    // cast operation
    operator void *() const { return p_; }

private:
    void * p_;
    Resource & res_;
};

The problem here is that (a) the Handle has to keep a reference to the Resource, and (b) the Resource needs to be able to cast the Handle to a void *. Unfortunately this leads to a circular dependency.

Any ideas on how to restructure this?

NOTE: The answer is not to simply "include xxx.hpp" or forward declare one of the classes. This needs to be restructured somehow, I just can't quite see how.

Adding a class Handle as a forward declaration to the top of the Resource file doesn't work, because the (void *) cast is part of the Handle definition that Resource still can't see. Likewise, changing the cast to a void * ptr() member function leads to the same problem.

Moving the function definitions to a .cpp file is also not an answer -- it needs to be header-only.

Upvotes: 0

Views: 1629

Answers (2)

David H
David H

Reputation: 1625

Well, it's templates to the rescue (AGAIN!):

// resource.hpp
class Resource;
template<typename TResource> class Handle;

class Resource {
public:
    // RAII constructor, destructor, etc.
    // ...
    void do_something(const Handle<Resource> & h) {
        do_something_impl( (void *) h);
    }
};

// handle.hpp
template<class TResource>
class Handle {
public:
    Handle(size_t n, const TResource & res)
        : p_(res.allocate(n)), res_(res) {}

    // cast operation
    operator void *() const { return p_; }

private:
    void * p_;
    TResource & res_;
};

Upvotes: 4

Some programmer dude
Some programmer dude

Reputation: 409404

Don't include the header files into each other, instead you have a forward declaration the classes. This way they can be used as references or pointers.

So in resource.hpp:

class Handle;

class Resource {
public:
    // RAII constructor, destructor, etc.
    // ...
    void do_something(const Handle & h) {
        do_something_impl( (void *) h);
    }
};

And in handle.hpp:

class Resource;

class Handle
{
public:
    Handle(size_t n, const Resource & res)
        : p_(res.allocate(n)), res_(res) {}

    // cast operation
    operator void *() const { return p_; }

private:
    void * p_;
    Resource & res_;
};

Since you use the void* typecasting operator inside the do_something function, you have to move that implementation into a separate source file. In that source file you can include both header files, so all functions can be accessed.

Upvotes: 1

Related Questions