Here it is, this time with working code and with the "loan" and
"transfer" names. I find them much better for explaining this, thanks
Andrew!
One important simplification: proton functions will take parameters as
plain foo&. I was originally going to write a pointer-like class with
the exact same semantics as foo& and then try to explain it. Duh.
Restricting the fancy pointer template stuff to return values makes it
easier to explain (probably because explaining why you are doing
something you really don't need to do at all is always puzzling to the
listener.)
Any more suggestions, shout quick. This will hit master soon and it'll
be too late.
/**
@page memory_management
The C++ proton binding offers the following options, in decreasing order of
safety and increasing order of complexity. You don't need to understand the more
complex options to use the simpler ones.
1. memory-safe use with smart pointers (std::shared_ptr, std::unique_ptr, boost::shared_ptr, boost::intrusive_ptr)
2. unsafe plain-pointer use to avoid smart pointer overhead.
3. exchange of pointers between C and C++ code.
In this discussion `foo` is a placeholder for any proton C++ class such as
`proton::reactor::link` or `proton::reactor::session`.
## Safe smart-pointer use.
Proton functions return either `pn_loan_ptr<foo>` or `pn_transfer_ptr<foo>`.
They are there to correctly convert to different smart pointer types. Don't
worry about them, don't use them in your own code, just *assign* the return
value to the smart pointer of your choice: std::shared_ptr, std::unique_ptr,
boost::shared_ptr, boost::intrusive_ptr or std::auto_ptr.
If you are stuck with old C++ and no boost library you can use
`proton::pn_shared_ptr` in your code. It does not have all the features of
std::shared_ptr but does provide basic memory safety.
## Unsafe plain pointer use.
If you use plain pointers there are no extra reference counts or allocations
compared to using the C API directly, but you are responsible for following the
rules below. Use `get()` to convert a function return value to a plain pointer.
If a function returns `pn_transfer_ptr<foo>`, ownership is transferred to you
and you are responsible for deleting. For example:
proton::reactor::receiver* r1 = session->create_receiver(...).get();
...
delete r1;
If a function returns `pn_loan_ptr<foo>` then the pointer is on loan to you.
You may use it in some limited scope (see the doc) but you must not delete it,
and you must not use it outside that scope. For example:
class my_handler : public proton::messaging_handler {
...
void on_link_opened(proton::reactor::event &e) {
proton::reactor::link* l = e.link()
// Use it but don't delete it.
...
// On return, l is invalid. Don't save it for later use. If you
// need to, use a smart pointer instead.
}
}
Note: If you assign the result of a pn_loan_ptr function to a smart pointer,
everything works automatically because of the magic described in the next
section. When you call get() the magic is gone and you can't get it back. For
example, proton::reactor::event::link(...) returns pn_loan_ptr<link>, so the
pointer is on *loan*. That doesn't matter if you use shared pointers:
std::unique_ptr<proton::reactor::link> l = e.link();
This is safe, l is valid even if you return it outside the current scope.
However the following will crash. The pointer is on loan, so the caller must
not delete it, either directly or indirectly by making a smart pointer own it.
std::auto_ptr<proton::reactor::link> l(e.link().get()); // CRASH
std::unique_ptr<proton::reactor::link> l(e.link().get()); // CRASH
std::shared_ptr<proton::reactor::link> l(e.link().get()); // CRASH
## Gory details and mixed C/C++
You don't have to read this unless you are interested in how it works or in
using mixed C and C++ code.
Here are the key things to know:
1. The public `C` API contains named but undefind C structs, e.g. `struct
::pn_foo_t;` You can `reinterpret_cast<>` between `foo*` and `pn_foo_t*`. They
point at the same memory location (the C struct) so you can pass between C and
C++.
2. Given `foo* p`, calling `p->something(...)` is equivalent to the C call
`::pn_foo_something(reinterpret_cast<::pn_foo_t*>(p, ...))`
3. As well as `pn_foo_free()` for traditional "free-style" memory management,
each proton C struct also has a reference counts. `pn_object_incref/decref()`
manipulate them.
4. Proton over-rides `operator delete` so `delete p` is equivalent to
`pn_object_decref(reinterpret_cast<pn_foo_t*>(p))`
When you call `delete`, you are deleting a *proton reference*. The underlying
proton object is only deleted if it is the *last* reference. The object lives on
until the last internal or external reference is gone.
pn_loan_ptr increases the refcount when converted to a smart pointer so the
smart pointer has a new reference that it owns. pn_transfer_ptr doesn't change
the refcount so the smart pointer takes ownership of the existing reference.
The get() function does nothing to the refcount, so the programmer must follow
proton's rules about valid scope and `delete`. It is an explicit function rather
than a conversion to avoid confusion with smart pointer constructors that take
ownership of a plain pointer.
One more trick. If we made a new shared_ptr on every conversion, that would be a
new shared_ptr *family* per call, not just an *instance*. There is one more
secret:
5. The first time someone wants a shared_ptr to a given proton object we
allocate a weak_ptr and stash it in a context slot on the object. Subsequently
we use that as a shared_ptr factory. Thus there is only one shared_ptr family
per object, and only if you ask for it.
If you need to know more, read the source!
*/
#if (not defined PN_USE_CPP11 && (defined(__cplusplus) && __cplusplus >= 201100) || (defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 150030729))
#define PN_USE_CPP11 1
#endif
#include <iostream>
#include <memory>
using namespace std;
// ==== Simulate private proton .h files.
// Fake pn object.
struct pn_foo_t {
string s_; // Dummy state
int refs_; // Simulate the external refcount
// The "external" refcount is the number of c++ shared_ptr *groups* referencing the object.
// This is in addition to any internal library refs - for this test we don't care about those
// so we start refs_ at 0, meaning no external references.
//
// It is OK to have multiple shared_ptr groups pointing at the same pn_foo_t
// since the end of a shared_ptr group only *decrefs* the pn_foo, it doesn't
// delete it.
//
pn_foo_t(string s) : s_(s), refs_(0) { cout << s_ << " created" << endl; }
~pn_foo_t() { cout << s_ << " freed" << endl; }
};
// ==== Simulate public proton C API and proton internal refcounts.
pn_foo_t* pn_foo_create(string s) { return new pn_foo_t(s); } // 0 external refs
string pn_foo_get_s(pn_foo_t* p) { return p->s_; }
void pn_foo_set_s(pn_foo_t* p, string s) { p->s_ = s; }
void pn_foo_free(pn_foo_t* p) { delete p; }
// Simulate pn_object_incref/decref
void pn_object_incref(void* v) {
pn_foo_t* p = reinterpret_cast<pn_foo_t*>(v);
++p->refs_;
}
void pn_object_decref(void* v) {
pn_foo_t* p = reinterpret_cast<pn_foo_t*>(v);
--p->refs_;
if (p->refs_ == 0) delete p;
}
// ==== Simulate C++ binding
// Placeholder, real definition in proton/types.hpp. Generates all comparison ops from < and ==.
template <class T> class comparable {};
template <class T> class pn_shared_ptr;
/**@internal
*
* This is the main engine for pointer conversion, used only as a base
* class. Derived types provide a `give()` function which returns a pointer
* that can be deleted safely by the caller. `Derived::give()` will incref or not,
* the derived classes know if we are in a "loan" or "transfer" situation.
* (No virtual functions, uses the CRTP.)
*/
template <class T, class Derived> class pn_ptr : public comparable<pn_ptr<T, Derived> > {
public:
typedef T element_type;
typedef typename T::pn_type pn_type;
T* get() const { return ptr_; }
pn_type* get_pn() const { return reinterpret_cast<pn_type>(ptr_); }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
bool operator!() const { return !ptr_; }
void swap(pn_ptr& x) { std::swap(ptr_, x.ptr_); }
operator pn_shared_ptr<T>() { return pn_shared_ptr<T>(give()); }
operator std::auto_ptr<T>() { return std::auto_ptr<T>(give()); }
#if PN_USE_CPP11
// FIXME aconway 2015-08-21: need weak pointer context for efficient shared_ptr
operator std::shared_ptr<T>() { return std::shared_ptr<T>(give()); }
operator std::unique_ptr<T>() { return std::unique_ptr<T>(give()); }
#endif
#if PN_USE_BOOST
// FIXME aconway 2015-08-21: need weak pointer context for efficient shared_ptr
operator boost::shared_ptr<T>() { return boost::shared_ptr<T>(give()); }
operator boost::intrusive_ptr<T>() { return boost::intrusive_ptr<T>(give()); }
#endif
// FIXME aconway 2015-08-20: template conversions for pointers to related types as per std:: pointers.
protected:
pn_ptr(pn_type* p) : ptr_(reinterpret_cast<T*>(p)) {}
T* incref() const { pn_object_incref(reinterpret_cast<typename T::pn_type*>(ptr_)); return ptr_; }
T* decref() const { pn_object_decref(reinterpret_cast<typename T::pn_type*>(ptr_)); return ptr_; }
T* give() { return static_cast<Derived*>(this)->give(); }
T* ptr_;
};
template <class T, class D> inline
bool operator==(pn_ptr<T,D> x, pn_ptr<T,D> y) { return x.get() == y.get(); }
template <class T, class D> inline
bool operator<(pn_ptr<T,D> x, pn_ptr<T,D> y) { return x.get() < y.get(); }
/**
pn_shared_ptr is a smart pointer template that uses proton's internal
reference counting. Proton objects hold their own reference count so there is
no separate "counter" object allocated. The memory footprint is the same as a
plain pointer.
It is provided as a convenience for programmers that have pre-c++11 compilers
and cannot use the boost libraries. If you have access to std::shared_ptr,
boost::shared_ptr or boost::intrusive_ptr you can use them instead.
*/
template <class T> class pn_shared_ptr : public pn_ptr<T, pn_shared_ptr<T> > {
public:
pn_shared_ptr(T* p) : pn_ptr<T, pn_shared_ptr<T> >(p) {}
pn_shared_ptr(const pn_shared_ptr<T>& p) : pn_ptr<T, pn_shared_ptr<T> >(p) { this->incref(); }
~pn_shared_ptr() { decref(ptr_); }
// FIXME aconway 2015-08-20: C++11 move constructor
void reset(T* p) { this->decref(); ptr_ = p; }
pn_shared_ptr& operator=(const pn_shared_ptr<T>& p) { reset(p.incref()); return *this; }
private:
T* ptr_;
T* give() { return this->incref(); } // A new reference, we keep the original.
friend class pn_ptr<T, pn_shared_ptr<T> >;
};
/**
pn_loan_ptr is used as a return value in proton functions. It will convert
automatically and safely to any smart pointer type. @see \ref memory_management
*/
template <class T> class pn_loan_ptr : public pn_ptr<T, pn_loan_ptr<T> > {
// Public members inherited from pn_ptr.
private:
pn_loan_ptr(typename T::pn_type* p) : pn_ptr<T, pn_loan_ptr<T> >(p) {}
T* give() { return this->incref(); } // A new reference, don't touch the original.
friend class pn_ptr<T, pn_loan_ptr<T> >;
friend class foo;
friend class event;
};
/**
pn_transfer_ptr<> is used as a return type in proton::functions when. It will convert
automatically and safely to any smart pointer type. @see \ref memory_management
*/
template <class T> class pn_transfer_ptr : public pn_ptr<T, pn_transfer_ptr<T> > {
// Public members inherited from pn_ptr.
private:
pn_transfer_ptr(typename T::pn_type* p) : pn_ptr<T, pn_transfer_ptr<T> >(p) {}
T* give() { return this->get(); }
friend class pn_ptr<T, pn_transfer_ptr<T> >;
friend class foo;
};
class foo;
///@internal traits to deduce cpp type from pn_type
template <class P> struct cpp_type{};
template <> struct cpp_type<pn_foo_t> { typedef foo type; };
template <> struct cpp_type<const pn_foo_t> { typedef const foo type; };
//... etc.
/// The public foo wrapper class.
class foo {
public:
typedef pn_foo_t pn_type;
string s() const { return pn_foo_get_s(reinterpret_cast<pn_foo_t*>(const_cast<foo*>(this))); }
void s(string s_) { pn_foo_set_s(reinterpret_cast<pn_foo_t*>(this), s_); }
// Public C++ API for user to create a foo.
static pn_transfer_ptr<foo> create(string s) {
return pn_transfer_ptr<foo>(pn_foo_create(s));
}
void operator delete(void* p) { pn_foo_free(reinterpret_cast<pn_foo_t*>(p)); }
};
// Simulated event handler.
class event {
public:
event(string name) : name_(name), pn_foo_(pn_foo_create(name)) {}
pn_loan_ptr<class foo> foo() {
return pn_loan_ptr<class foo>(pn_foo_);
}
private:
string name_;
pn_foo_t* pn_foo_;
};
class handler {
public:
virtual void on_foo(event&) = 0;
};
void run(string name, handler& h) {
event e(name);
h.on_foo(e);
}
// ==== User code
struct raw_handler : public handler {
foo* p;
void on_foo(event& e) { p = e.foo().get(); }
};
#if PN_USE_CPP11
struct shared_handler : public handler {
shared_ptr<foo> p;
void on_foo(event& e) { p = e.foo(); }
};
struct unique_handler : public handler {
unique_ptr<foo> p;
void on_foo(event& e) { p = e.foo(); }
};
#endif
int main(int, char**) {
foo* p = foo::create("raw").get(); // raw user create
delete p; // manual delete
raw_handler h;
run("raw-handler", h); // raw internal create.
cout << "after raw-handler: " << h.p->s() << endl; // still exists
delete h.p; // manual delete
// foo* foo_leak = foo::create("leak").get(); // raw no delete, should leak.
#if !PN_USE_CPP11
{
auto_ptr<foo> p(foo::create("auto_ptr").get()); // user create
}
#endif
#if PN_USE_CPP11
{
shared_ptr<foo> p = foo::create("shared"); // user create
}
{
shared_handler h;
run("shared-handler", h); // shared internal create.
cout << "after shared-handler: " << h.p->s() << endl; // still exists
}
{
unique_handler h;
run("unique-handler", h); // unique internal create.
cout << "after unique-handler: " << h.p->s() << endl; // still exists
}
{
unique_ptr<foo> p = foo::create("unique_ptr"); // user create
}
#ifdef BROKEN
// Compile error, you must specify a type.
auto p = foo::create("auto");
#endif
#endif
}