0

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.

David H
  • 1,461
  • 2
  • 17
  • 37

2 Answers2

4

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_;
};
David H
  • 1,461
  • 2
  • 17
  • 37
1

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.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • @Joachim--In vs2010, this gives "Cannot convert from 'const Handle & h' to 'void *'; Source or target has incomplete type. In other words, Resource can't see the `operator void *()`. – David H Jan 08 '13 at 14:52
  • Also, adding `class Resource` to handle.hpp won't work, because Handle needs to see the Resource.allocate() member function! – David H Jan 08 '13 at 14:57
  • @DavidH Then you have to include the `resource.hpp` file in `handle.hpp`. As long as you don't do the opposite too it's okay. As for the error, it's because you are trying to convert a non-pointer type to a pointer type. – Some programmer dude Jan 08 '13 at 15:39
  • @Joachim--The whole point of the conversion operator is **specifically** to convert the Handle type to a pointer. Can you suggest a different way? A void * ptr() member function would give the same error and cause the same problem. – David H Jan 08 '13 at 15:41
  • @DavidH Can't you just use the address-of operator to actually get a real pointer? – Some programmer dude Jan 08 '13 at 15:57
  • @Joachim--No, I specifically need the value of `p_`, the void * pointer encapsulated in the Handle. This is the purpose of the Handle class, to encapsulate both the pointer and the Resource it came from. – David H Jan 08 '13 at 15:59
  • @DavidH Ah I see it now... In that case the only solution I see is to put the implementation of the `do_something` function in a source file. If you have that you can include both header files in the source file, and the problem should go away. – Some programmer dude Jan 08 '13 at 16:48
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/22412/discussion-between-david-h-and-joachim-pileborg) – David H Jan 08 '13 at 19:04