#ifndef __SmarterP_h_ #define __SmarterP_h_ #include #include #include #include "MiscSupport.h" ///////////////////////////////////////////////////////////////////// // SmarterP is a thread safe reference counted smart pointer. // Multiple SmarterP items can all point to the same object. When // the last one goes away, the object will automatically be deleted. // // SmarterCP is a constant version of SmarterP. That is to say the // object that we are pointing to is const. SmarterP and SmarterCP // objects are compatible as long as they were based on the same // template argument. So // SmarterP< int > notConst; // int *notConst // SmarterCP< int > isConst; // int const *isConst; // isConst = notConst; // Legal. Automatically converted to const. // notConst = isConst; // ILLEGAL! Won't compile. // // This uses the standard Posix semantics. If you have an object // of type SmarterP (or SmarterCP) only one thread at a time should // access the object. However, the internals are thread safe. If p1 // and p2 are both pointing to the same object, there's no reason we // can't access p1 and p2 at the same time in different theads. // // Note that we're only discussing the SmarterP object itself, // and the SmarterP::Container that it creates internally. The object // you are pointing to might or might not be thread safe. That's // entirely up to you. // // One common strategy is to build and configure an object as a // SmarterP, then export it as a SmartCP. So the underlying object // is non const for a short time, but only before we share it. After // that we always treat the underlying object as const. Most C++ // objects are completely thread safe as long as they are const. // // There are several options when it comes to smart pointers. Here // are a few that I've used in this project. // RefCount -- This was as very simple smart pointer. It didn't have // any support for constness or thread safety. Those // weren't an issue at the time. // TSRefCount -- This is similar to RefCount, but I added constness // and thread safety. // std::shared_ptr -- This is the standard library's version of a // smart pointer. I might have used it if it was // around when I first needed it. It is better // than TSRefCount in some ways but worse in // others. // SmarterP -- I got annoyed when I saw that std::shared_ptr was // faster than either of my classes in certain cases. // But my classes were better in other ways. SmarterP // is arguably better than all of the other classes. // Each feature of SmarterP is the same as or better than // the similar feature in any of the other types. // // Object footprint: // If you create a variable or field of type SmarterP, TSRefCount, // or RefCount, you will reserve 8 bytes, the size of one pointer. // std::shared_ptr takes twice as much space. // // Dyamic memory: // RefCount, TSRefCount, and std::shared_ptr all require two seperate // pieces of dynamic memory. One for the object we're pointing to. // The second holds the reference count and possibly other metadata. // SmarterP allocates both in the same block. So we only need to // store one pointer. And fewer accesses to new and delete. // // Dereferencing the pointer: // If you have a RefCount or a TSRefCount, and you want to access the // object that it's holding, you have to dereference two normal // pointers and compare one of them to 0. These two blocks of memory // were allocated seperately, and are probbly not in the same cache // block. std::shared_ptr and SmarterP let you access your data by // dereferencing a single normal pointer. This happens a lot, so // this might be the most important issue. // // Naked pointers: // With RefCount, TSRefCount, and std::shared_ptr you always start // by calling new to get a normal pointer, then you give that pointer // to the shared pointer. SmarterP completely hides the pointer from // you. In some cases there's some complicated code between where // you create the raw pointer and where you wrap it in the smart // pointer. With SmarterP you never see the pointer and there is // no time when you could accidentally get a memory leak because you // exited early. ///////////////////////////////////////////////////////////////////// // SmarterPBase is used to implement SmarterP and SmarterCP. Don't // use it directly. Notice the public methods that are written here // and avilable in all SmarterP and SmarterCP objects. template< class T > class SmarterPBase { private: typedef uint64_t Counter; struct Container { T value; Counter count; template< class... Args > // General info on std::forward() and friends: // http://bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html // Specific instructions for mixing && with ...: // https://stackoverflow.com/questions/2821223/how-would-one-call-stdforward-on-all-arguments-in-a-variadic-function Container(Args&&... args) : value(std::forward(args)...), count(1) {} } *_container; protected: void decrement() { if (_container) { // Looking at the assembly code, __sync_fetch_and_sub is directly // supported by the processor. __sync_sub_and_fetch is synthisized // using a few instructions, and an extra register. const Counter before = __sync_fetch_and_sub(&_container->count, 1); if (before == 1) delete _container; } // This leaves the oject in an invalid state. Either call this // in a destructor, or set _container NULL after calling this method, // or something similar. } void increment() { if (_container) { // If you ignore the result, the compiler is smart enough to treat // __sync_add_and_fetch the same as __sync_fetch_and_add. Even without // optimization turned on. __sync_fetch_and_add(&_container->count, 1); } } SmarterPBase() : _container(NULL) { //std::cout<<"SmarterPBase("<value; } public: // This will create a new object of the underlying type. args will be // passed to that obect's constructor. The smart pointer will be pointing // to that new object. If the smart pointer was pointing to something else, // that is released first. template< class... Args > void emplace(Args&&... args) { decrement(); _container = new Container(std::forward(args)...); //_container = new Container(args...); } // Standarad pointer rules. operator bool() const { return _container; } bool operator !() const { return !_container; } bool operator ==(SmarterPBase const &other) const { return _container == other._container; } bool operator !=(SmarterPBase const &other) const { return _container != other._container; } bool operator <(SmarterPBase const &other) const { return _container < other._container; } bool operator <=(SmarterPBase const &other) const { return _container <= other._container; } bool operator >(SmarterPBase const &other) const { return _container > other._container; } bool operator >=(SmarterPBase const &other) const { return _container >= other._container; } // This means that this smart pointer is the only smart pointer pointing // to this object. It is an error to call this on a NULL pointer. bool isUnique() const { return _container->count == 1; } // Same as if we were using a normal pointer. size_t hash_code() const { return std::hash< Container * >()(_container); } // Point to NULL. If necessary decrement a reference count and possibly // delete the underlying object. void clear() { decrement(); _container = NULL; } }; // SmarterCP -- Smarter Const Pointer. template< class T > class SmarterCP : public SmarterPBase< T > { private: typedef SmarterPBase< T > Base; public: // Default constructor. The object will point to NULL. SmarterCP() { } // This constructor will create a new underlying object and will point // this object to it. This is helpful if you want to make a SmarterCP // variable const. The first argument is NULL, just so we can recognize // this request, and the rest of the arguments are passed on to the // underlying object's constructor. template< class... Args > SmarterCP(std::nullptr_t unused, Args&&... args) { this->emplace(std::forward(args)...); } // Copy constructor. Note that you can use this to copy a pointer from // a SmarterP or another SmarterCP. That is, we'll automatically add // const to the underlying object. You cannot go the other way. SmarterCP(Base const &other) : Base(other) { } /* TSRefCount(TSRefCount &&other) : TSRefCountBase< baseType >(std::move(other)) { } */ // Assignment. Note that you can use this to copy a pointer from // a SmarterP or another SmarterCP. That is, we'll automatically add // const to the underlying object. You cannot go the other way. SmarterCP &operator =(Base const &other) { //std::cout<assign(other); return *this; } // Assignment. This does the same thing as the previous assignment // statement. You need this to keep C++ happy. If I didn't explicitly // create this function, C++ would create it's own, and that default // would not update the reference count. SmarterCP &operator =(SmarterCP const &other) { //std::cout<assign(other); return *this; } // Assignment. Use move semantics. SmarterCP &operator =(Base &&other) { //std::cout<clear(); this->swap(other); return *this; } // Allow someone to say ptr = NULL. This makes the smart pointer look more // like a normal pointer. It seems like this could be defined in the base // class, rather than here, but C++ would not find this method until I moved // it here. void operator =(std::nullptr_t) { this->clear(); } // Dereference the pointer. Despite jumping through some hoops in // the source code, the object code should be very tight. Effectively // we're just saying return (T *)_container. Basically the same as // if this was a normal pointer instead of a SmarterCP object. T const &operator *() const { return *Base::getPointer(); } T const *operator ->() const { return Base::getPointer(); } }; // SmarterP -- Smarter Pointer. template< class T > class SmarterP : public SmarterPBase< T > { private: typedef SmarterPBase< T > Base; public: // Default constructor. The object will point to NULL. SmarterP() { } // This constructor will create a new underlying object and will point // this object to it. This is helpful if you want to make a SmarterP // variable const. The first argument is NULL, just so we can recognize // this request, and the rest of the arguments are passed on to the // underlying object's constructor. template< class... Args > SmarterP(std::nullptr_t unused, Args&&... args) { this->emplace(std::forward(args)...); } // Copy constructor. SmarterP(SmarterP const &other) : Base(other) { } /* NCTSRefCount(NCTSRefCount &&other) : TSRefCountBase< baseType >(std::move(other)) { } */ // Assignment. SmarterP &operator =(SmarterP const &other) { this->assign(other); return *this; } // Allow someone to say ptr = NULL. This makes the smart pointer look more // like a normal pointer. It seems like this could be defined in the base // class, rather than here, but C++ would not find this method until I moved // it here. void operator =(std::nullptr_t unused) { this->clear(); } // Dereference the pointer. Despite jumping through some hoops in // the source code, the object code should be very tight. Effectively // we're just saying return (T *)_container. Basically the same as // if this was a normal pointer instead of a SmarterCP object. T &operator *() const { return *Base::getPointer(); } T *operator ->() const { return Base::getPointer(); } // One strategy for using smart pointers to share data. // 1) Create a variable pointing to a non-const smart pointer pointing to the // official current state. // 2) Put the variable in a mutex or Locker. // 3) If you want to read the data: // a) Lock the mutex // b) Grab a copy of the smart pointer from the variable. // c) Make sure you grab a const version of the pointer. // d) Unlock the mutex. You held it for a very brief time. // e) Hold the const smart pointer as long as you need it, but no longer. // 4) If you want to change the data: // a) Lock the mutex // b) Call unshare() on the original smart pointer variable. // c) Modify the object through the variable and the smart pointer. // d) Unlock the mutex. // // Picture a large std::map in the data. From time to time you come in and // make a relatively small change, maybe add one new entry and modify another. // From time to time several threads will want to read the data. They often // want to do slower and/or more complicated things. Like iterate over the // entire map(). Maybe look at the map several times in a row, but mixed in // with other work that might be slow. // // This strategy is very simple to use. You don't have a lot of rules to // follow or work to do. Basically you're grabbing a pointer to the data // and doing what you need to with it. // // Behind the scenes the SmarterP is copying the data for you. But in a lazy // fashion. It waits until the last second and only does the copy if it really // needs to. void unshare() { if ((*this) && !this->isUnique()) this->emplace(**this); } }; // Adapted from http://en.cppreference.com/w/cpp/utility/hash // Strange. I tried adding just one rule to cover the base // class, SmarterPBase. That didn't work. When I tried to // instantiate a hash table for SmarterCP, g++ couldn't find // anything. So instead I added two separate rules, one for // each of the derived classed. namespace std { template< class T > struct hash< SmarterP< T > > { typedef SmarterPBase< T > argument_type; typedef std::size_t result_type; result_type operator()(argument_type const& p) const { return p.hash_code(); } }; template< class T > struct hash< SmarterCP< T > > { typedef SmarterPBase< T > argument_type; typedef std::size_t result_type; result_type operator()(argument_type const& p) const { return p.hash_code(); } }; } ///////////////////////////////////////////////////////////////////// // SmarterPVar is a thread-safe way to share SmarterP and SmarterCP // objects. All access is copy-on-write. // // A constant problem with various C++ smart pointers is that the // smart pointer object itself is not thread safe. Typically you can // copy a smart pointer out of a variable, or use it in place, as long // as only one thread at a type has access to that variable. This // object adds a mutex to manage access to the smart pointer. // // You are encouraged to make a local copy of the smart pointer. // For one thing, you will receive a read only copy of the data, so // it will not change under you. Each time you request a copy of the // smart pointer, you might be getting a different pointer to a // different object. // // Also, it's a little more efficient if you keep a copy. // SmarterPVar uses a mutex every time you access the data. // SmarterP (like most smart pointers) does not require a mutex. // // Notice the copy on write semantics. You can ask for a read only // smart pointer that you can hold on to as long as you like. Or you // can ask for a writable pointer. That uses unshare() to avoid making // a copy of the data unless that's required. That holds a lock, so // try to make the update quick. // // This is similar to working with transactional tables in MySql. // When you read you get a smart pointer, like starting a transaction // in MySql, you can hold onto that as long as you like. Multiple // people can be holding different smart pointers, like multiple // threads in MySql holding different versions of the table. Write // access is effectively serialized. This data structure is slightly // different because the write lock blocks any new read locks. MySql // transactional tables allow new read requests to grab the last // committed version while a write is in progress, but we do not. ///////////////////////////////////////////////////////////////////// template < typename T > class SmarterPVar { public: // Mostly used internally. typedef SmarterP< T > Pointer; // The easiest way to access the data is to get a read only reference // counted copy of the data. typedef SmarterCP< T > ConstPointer; private: // _pointer contains the actual data. _pointer is always protected // by _mutex. mutable pthread_mutex_t _mutex; void lock() const { assertFalse(pthread_mutex_lock(&_mutex)); } void unlock() const { assertFalse(pthread_mutex_unlock(&_mutex)); } SmarterP< T > _pointer; // This is private. Use get() for a const version of the same data. Pointer getPointer() const { lock(); Pointer result = _pointer; unlock(); return result; } public: // Create a new SmarterPVar initialized to the given data. // // We need a version of the data that can be modified. However, we // should have the only modifiable copy of the data. If you hold onto // the original data and modify it after you put into here, that will // break the rules. In particular, if someone calls get() they expect // a pointer to an object that will never change. SmarterPVar(Pointer const &pointer = Pointer()) : _pointer(pointer) { assertFalse(pthread_mutex_init(&_mutex, NULL)); } // Create a new SmarterPVar initialized to be a copy of the other // SmarterPVar. This is fast because we are just incrementing a // reference count. This preserves copy on write. If you modify // either variable, it will not affect the other. If necessary // this class will automatically copy the underlying data before // making a change. SmarterPVar(SmarterPVar< T > const &other) : SmarterPVar(other.getPointer()) { } // Returns a smart pointer to a read only copy of the data. This // data will not change even if the variable does change. ConstPointer get() const { return getPointer(); } // Assigns a SmarterP to this SmarterPVar. Discards any pointer // that was previously in the SmarterPVar. // // Same rules as the constructor. The caller needs to provide // a smart pointer that is not const, and the caller should not // directly access the smart pointer after that. Use // SmarterPVar::get(), or just convert the original SmarterP to // a SmarterCP. void operator =(Pointer const &pointer) { lock(); _pointer = pointer; unlock(); } // Copy the value from the other SmarterPVar into this one. Does // a copy on write: This initial assignment is very quick, but // the next attempt to modify this value or the original might // cause SmarterPVar to automatically copy the data. Discards // any pointer that was previously in this SmarterPVar. void operator =(SmarterPVar const &other) { lock(); _pointer = other.getPointer(); unlock(); } // Setting a SmarterPVar to NULL will work as expected, discarding // any data we were holding. void operator =(std::nullptr_t) { lock(); _pointer.clear(); unlock(); } // This is a way to update the value in place. // // This will lock the data so no one can call get() while the // update is taking place. If you already have copy from a // previous call to get(), you can continue to use it. // // This uses SmarterP::unshare() to take care of the copy-on-write. // // Updater should be a function that takes one argument, a &T. // Updater can make whatever changes it wants to the underlying // data. // // If the pointer is NULL this is a no-op. template< typename U > void change(U const &updater) { lock(); if (_pointer) { _pointer.unshare(); T &ref = *_pointer; updater(ref); } unlock(); } // Make a copy of the underlying data. Wrap that in a non-const smart // pointer. If the input was null, the output will be null. Pointer clone() const { Pointer result; const ConstPointer original = getPointer(); if (original) result.emplace(*original); return result; } // Standard meanings. If this is holding a NULL smart pointer, then // this object will be interpreted as false. Otherwise it will be // interpreted as true. operator bool() const { return getPointer(); } bool operator !() const { return !getPointer(); } // Create a new value of type T and store it in this SmarterPVar. // Abandons any previous value in here. The arguments to this method // are forwarded as is to T's constructor. template< class... Args > void emplace(Args&&... args) { *this = Pointer(NULL, std::forward(args)...); } /* Would these work? I'm afraid the smart pointer will be released when * we return, so the value we are returning might become a dangling * reference. It would be a race condition because it depends if another * thread releases the last reference to the smart pointer. T const &operator *() const { return *getPointer(); } T const *operator ->() const { return &*getPointer(); } */ }; #endif