Search code examples
c++circular-dependencyraii

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.


Solution

  • 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_;
    };