2

Introduction

Peter Weinhart describes how to design a generic intrusive_ptr base class using CRTP, which may be used as follows:

class foo : intrusive_base<foo>
{
     // foo-specific code.
};

This approach imposes the constraint that all foo objects carry a reference counter. Assume that we keep foo sometimes by value and only want to pay the price of the reference counter when we have a pointer. For example, sometimes we would like to create foo instances and just move them around, and sometimes we want to allocate foo on the heap.

Conceptually, the right mechanism for this scenario is a std::shared_ptr. However, there are certain scenarios requiring raw pointers that would call for an intrusive pointer, e.g., when passing pointers through a C API that take void pointers. In this case, one would "ref" the pointer before passing it to the opaque API and "unref" when getting it back.

Having control over foo, probably the best method would be to use a policy-based implementation and have a reference-counted and basic version of foo. Without having control over foo, an alternative design would to invert the inheritance relationship:

template <typename Base>
class intrusive : public Base
{
    // ?

private:
    std::atomic_size_t ref_count_;   
};

typedef intrusive<foo> intrusive_foo;

// Assume boost::intrusive_ptr as intrusive pointer implementation
boost::intrusive_ptr<intrusive_foo> x = new intrusive_foo;
{
    auto y = x;   // Semantics: boost::intrusive_ptr_add_ref(x.get())

    // At scope exit: boost::intrusive_ptr_release(x.get())
}

In the above mentioned article, Peter says that a such a "generic implementation of [intrusive] would make use of C++0x variadic templates and perfect forwarding."

Question

How would the implementation of such a generic intrusive class look like? I could see that it may benefit from C++11 inheriting constructors, but it is unclear to me how one would in fact implement the body of intrusive using the mentioned tools.

mavam
  • 12,242
  • 10
  • 53
  • 87
  • "Assume that we keep `foo` sometimes by value and only want to pay the price of the reference counter when we have a pointer?" Then don't make the reference count *intrusive*. Types always have their members, whatever those members are. Unless you heap-allocate them, and if you do, then you may as well use a `shared_ptr`. If you don't want to pay the price for something based on how a type is used, this generally means that the "something" needs to not be a member of the class. – Nicol Bolas Mar 06 '12 at 02:19
  • 1
    "In this case, one would "ref" the pointer before passing it to the opaque API and "unref" when getting it back." You could do pretty much the same thing when using a non-intrusive pointer. Just keep around an extra `shared_ptr` instance somewhere until the given pointer comes back. – Nicol Bolas Mar 06 '12 at 02:21
  • @Nicol: Conceptually, a `shared_ptr` is indeed The Right Thing to use. However, I cannot pass it to a C-API that takes a `void*` and manually increase the reference count. This is an issue when the `shared_ptr` goes out of scope and the C library has still a copy of `shared_ptr::get()`. I'll clarify your first concern in the question. – mavam Mar 06 '12 at 03:04
  • 2
    For the objects that need to be passed to the C API, provide a shared_ptr to themselves as a member. The C API, presumably, lets you know when it is done with your raw pointer, so you can release the `shared_ptr` then. If several of your classes are used this way, consider making this a base class (a little like `std::enable_shared_from_this`, which stores a `weak_ptr` as a member of the object itself -- except you'd have a `shared_ptr` and it would be empty except while your object is in the rapacious clench of raw, bloodthirsty C code). – cvoinescu Apr 12 '12 at 13:55
  • That's a nice workaround to the problem. – mavam Apr 13 '12 at 02:12

1 Answers1

1

Using make_shared gives you the same efficiency as an intrusive pointer.

In this case, one would "ref" the pointer before passing it to the opaque API and "unref" when getting it back.

As someone else said, you can use enable_shared_from_this to get a shared_ptr back from a raw pointer (as long as there is at least one shared_ptr somewhere in the system which still owns the object)

But to answer the main question, I assume he means using variadic templates and perfect forwarding to define a constructor, which would look like:

template <typename Base>
  class intrusive : public Base
  {
    template<typename... Args>
      intrusive(Args&&... args)
      : Base(std::forward<Args>(args)...), ref_count_(0)
      { }

This allow you to construct the intrusive with any number of arguments of any type and they will be forwarded to the Base, so you can construct it with any arguments that could be used to construct a Base.

Another alternative would be to use C++11 inheriting constructors (which aren't implemented in any compiler AFAIK)

template <typename Base>
  class intrusive : public Base
  {
    using Base::Base;
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • This form of constructor forwarding is probably what the author meant. The inheriting constructor solution is even nicer, yet requires to correctly initialize the reference counter in the class definition. Unfortunately, the `enable_shared_from_this` method won't work in my case, as I cannot guarantee that the application will keep a shared pointer after passing the pointer to the C API. – mavam May 06 '12 at 18:51