I have a proposal to integrate C++ and proton C memory management, I need a sanity check.
Attached is an executable C++ sketch and test (pn_ptr.cpp) and a script (test.sh) that runs the combinations of g++/clang++ and c++11/c++03, as well as some tests to verify that we get compile errors to prevent mistakes. The idea is that every pn_foo_t type has a corresponding C++ foo class with member functions to make it easy to call pn_foo_* functions in C++ (converting std::string/char* etc.) The first trick: the foo class is empty and never instantiated. A foo* actually points to the same memory location as the pn_foo_t*. You can reinterpret_cast between the two, and deleting the foo* will actually call pn_foo_free(pn_foo_t*). The next trick: proton::event accessor functions return a pn_ptr<foo>, which is an internal class that users cannot instantiate. What they can do is convert it by assignment or construction to any of: foo*, std::auto_ptr<foo>, std::unique_ptr<foo> or std::shared_ptr<foo>. In the shared_ptr case the conversion automatically does a pn_incref(). The upshot of this is that you can use plain foo* or any of the std::smart pointers to point to a foo and it Just Works. If you don't use shared_ptr you need to understand the proton C API lifecycle rules, but with shared_ptr it is all fully automatic. Moreover if you don't use shared_ptr there is almost no overhead over using pn_foo_t* directly in the C API, as the compiler should optimise away all the inline template magic. This works with c++11 (everything works) or c++03 (just foo* and auto_ptr). It will be trivial to add support for boost::shared_ptr so nice memory management will work with c++03 and boost.
/* I have a proposal to integrate C++ and proton C memory management, I need a sanity check. Attached is an executable C++ sketch and test (pn_ptr.cpp) and a script (test.sh) that runs the combinations of g++/clang++ and c++11/c++03, as well as some tests to verify that we get compile errors to prevent mistakes. The idea is that every pn_foo_t type has a corresponding C++ foo class with member functions to make it easy to call pn_foo_* functions in C++ (converting std::string/char* etc.) The first trick: the foo class is empty and never instantiated. A foo* actually points to the same memory location as the pn_foo_t*. You can reinterpret_cast between the two, and deleting the foo* will actually call pn_foo_free(pn_foo_t*). The next trick: proton::event accessor functions return a pn_ptr<foo>, which is an internal class that users cannot instantiate. What they can do is convert it by assignment or construction to any of: foo*, std::auto_ptr<foo>, std::unique_ptr<foo> or std::shared_ptr<foo>. In the shared_ptr case the conversion automatically does a pn_incref(). The upshot of this is that you can use plain foo* or any of the std::smart pointers to point to a foo and it Just Works. If you don't use shared_ptr you need to understand the proton C API lifecycle rules, but with shared_ptr it is all fully automatic. Moreover if you don't use shared_ptr there is almost no overhead over using pn_foo_t* directly in the C API, as the compiler should optimise away all the inline template magic. This works with c++11 (everything works) or c++03 (just foo* and auto_ptr). It will be trivial to add support for boost::shared_ptr so nice memory management will work with c++03 and boost. */ #if (not defined USE_CPP11 && (defined(__cplusplus) && __cplusplus >= 201100) || (defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 150030729)) #define 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_incref(void* v) { pn_foo_t* p = reinterpret_cast<pn_foo_t*>(v); ++p->refs_; } void pn_decref(void* v) { pn_foo_t* p = reinterpret_cast<pn_foo_t*>(v); --p->refs_; if (p->refs_ == 0) delete p; } // ==== Simulate C++ binding /** For proton object pn_foo_t functions in the C++ binding return pn_ptr<foo>. Users do not use pn_ptr<foo> directly, instead they assign or convert the result to `foo*` or `shared_ptr<foo>` to get raw or safe access respectively This does two things: - completely automates refcounts if the user always uses shared_ptr<foo> - imposes 0 overhead if the user uses foo* and leave the user responsible. The brave and foolish can mix the two, and can reinterpret_cast between foo* and pn_foo_t* to mix use of C++ and C APIs. That is NOT recommended. I would recommend one of the following 2 options: 1. Most users: Just use shared_ptr<foo>. Don't mess with the C API. Sleep at night. 2. Insane performance freaks and wild-eyed mixed C/C++ developers: use foo* and treat the C++ API as a C++ friendly version of the C API. In this mode (like a C programmer) you should probably never use proton refcounts: just learn the lifecycle rules and call pn_*_free() at the right times. See the C API for the rules. NOTE: endpoints (connection, session, link) have both _close and _free. shared_ptr automates calling _free and freeing memory. If you want a clean AMQP close you need to call close also. There will be a close_ptr<foo> to automate this, not illustrated below but you can imagine it. The C++ container will close things automatically for you if you don't close them yourself. */ 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. template <class T> class pn_ptr; template <class P> pn_ptr<typename cpp_type<P>::type> make_pn_ptr(P* p); /** pn_ptr is returned by functions in the C++ API. You should NOT use this type in your code. Most users should assign or convert pn_ptr<foo> to a shared_ptr<foo> for automatic memory management. If you understand the proton C memory management rules and need to avoid shared_ptr, you can convert pn_ptr<foo> to foo*. In that case you are responsible for deleting the pointer at the right time. You can use delete std::unique_ptr, std::auto_ptr etc. to do this. If you understand the C API in detail you can reinterpret_cast between foo* to pn_foo_t* and mix the C and C++ APIs but this is not recommended. */ template <class T> class pn_ptr { public: operator T*() { return reinterpret_cast<T*>(ptr_); } T* operator->() { return ptr_; } #if USE_CPP11 operator shared_ptr<T>() { pn_incref(ptr_); // Add a reference for the shared_ptr family return shared_ptr<T>(reinterpret_cast<T*>(ptr_), pn_decref); } operator unique_ptr<T>() { return unique_ptr<T>(reinterpret_cast<T*>(ptr_)); } #endif private: typedef typename T::pn_type P; pn_ptr(P* ptr) : ptr_(ptr) {} pn_ptr(const pn_ptr& x) : ptr_(x.ptr_) {} P* ptr_; friend pn_ptr<T> make_pn_ptr<>(P* p); friend class foo; friend class event; }; template <class P> pn_ptr<typename cpp_type<P>::type> make_pn_ptr(P* p) { return pn_ptr<typename cpp_type<P>::type>(p); } /// 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_ptr<foo> create(string s) { return make_pn_ptr(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_ptr<class foo> foo() { return make_pn_ptr(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(); } }; #if 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"); // 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"); // raw no delete, should leak. #if !USE_CPP11 { auto_ptr<foo> p(foo::create("auto_ptr")); // user create } #endif #if 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 }
test.sh
Description: application/shellscript