#ifndef __ThreadSafeRefCount_h_ #define __ThreadSafeRefCount_h_ #include #include #include ///////////////////////////////////////////////////////////////////// // This is a thread safe version of RefCount. // // This works like std::string, a file handle, and a lot of other // standard objects. If you have one varaible of this type, only // one thread at a time should access that variable. But if you // have two variables of this type, and the two variables are each // sharing the underlying object, that's okay. You can still access // each varaible in a different thread. // // (std::string was a terrible example. A search of the Internet says // that sharing strings between threads is generally dangerous and // the results are undefined! Some people suggest the standard // posix idea, described above, but others disagree.) // // We make the contents of the pointer const. This simplifies // things. Any number of threads can (typically) access a const // pointer without any trouble. And that's the way we planned // to use this. // // Note that it would not be possible to have the locks extend around // myTSRefObj->someSlowOperation(). At least not automatically, and // I don't want to export the locks. That's why I decided not to // make the individual TSRefCount objects thread safe. If you // couldn't make this fundamental operation thread safe, what's the // point of making the other operations thread safe. // // Initially we only had the read only version. This is a nice // model, but doesn't apply to everything. So I added a non-const // version. ///////////////////////////////////////////////////////////////////// template< class baseType > class TSRefCountBase { private: typedef uint64_t Counter; struct Container { Counter count; baseType *basePointer; //int64_t padding[5]; // This padding prevents false sharing. In some tests I found a small // improvement. Maybe up to a 20% savings. } *_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->basePointer; delete _container; } } } 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); } } TSRefCountBase(baseType *basePointer = NULL) { if (basePointer) { _container = new Container; _container->count = 1; _container->basePointer = basePointer; } else { _container = NULL; } } ~TSRefCountBase() { decrement(); } TSRefCountBase(TSRefCountBase const &other) { _container = other._container; increment(); } /* TSRefCountBase(TSRefCountBase &&other) { _container = other._container; other._container = NULL; } */ void assign(TSRefCountBase const &other) { if (this != &other) { decrement(); _container = other._container; increment(); } } /* void swap(TSRefCountBase &other) { auto temp = _container; _container = other._container; other._container = temp; } */ static void assign(TSRefCountBase &dest, TSRefCountBase const &src) { // This function might seem a little silly at first, but it is essential. // NCTSRefCount can call this version of assign() on any two objects of // type TSRefCountBase. If NCTSRefCount directly calls the one argument // version of assign(), the explicit "other" parameter can be any // TSRefCountBase object. But the implicit "this" parameter must be // a NCTSRefCount. If the caller is a TSRefCount object, then the implicit // "this" parameter must be another TSRefCount object. The static version // of this function allows us to convert between types when its is // appropriate. Note: NCTSRefCount is a template, so the compiler might // not find and report the problem with the one argument assign() as // soon as you might think. dest.assign(src); } baseType *getPointer() const { if (_container) { return _container->basePointer; } else { return NULL; } } public: operator bool() const { return _container; } bool operator !() const { return !_container; } bool operator ==(TSRefCountBase const &other) const { return _container == other._container; } bool operator !=(TSRefCountBase const &other) const { return _container != other._container; } bool operator <(TSRefCountBase const &other) const { return _container < other._container; } bool operator <=(TSRefCountBase const &other) const { return _container <= other._container; } bool operator >(TSRefCountBase const &other) const { return _container > other._container; } bool operator >=(TSRefCountBase const &other) const { return _container >= other._container; } bool isUnique() const { // It is an error to call this on a NULL pointer. // This means that this smart pointer is the only smart pointer pointing // to this object. return _container->count == 1; } size_t hash_code() const { return std::hash< Container * >()(_container); } void clear() { decrement(); _container = NULL; } }; template< class baseType > class TSRefCount : public TSRefCountBase< baseType > { public: TSRefCount(baseType *basePointer = NULL) : TSRefCountBase< baseType >(basePointer) { } TSRefCount(TSRefCount const &other) : TSRefCountBase< baseType >(other) { } /* TSRefCount(TSRefCount &&other) : TSRefCountBase< baseType >(std::move(other)) { } */ TSRefCount &operator =(TSRefCount const &other) { this->assign(other); return *this; } /* TSRefCount &operator =(TSRefCount &&other) { this->swap(other); return *this; } */ baseType const &operator *() const { return *TSRefCountBase< baseType >::getPointer(); } baseType const *operator ->() const { return TSRefCountBase< baseType >::getPointer(); } }; // NC stands for non-const. The const version is "the default". That doesn't // mean much, but it is slightly easier to use. template< class baseType > class NCTSRefCount : public TSRefCountBase< baseType > { public: NCTSRefCount(baseType *basePointer = NULL) : TSRefCountBase< baseType >(basePointer) { } NCTSRefCount(NCTSRefCount const &other) : TSRefCountBase< baseType >(other) { } /* NCTSRefCount(NCTSRefCount &&other) : TSRefCountBase< baseType >(std::move(other)) { } */ NCTSRefCount &operator =(NCTSRefCount const &other) { this->assign(other); return *this; } /* NCTSRefCount &operator =(NCTSRefCount &&other) { this->swap(other); return *this; } */ baseType &operator *() const { // Note: // const NCTSRefCount< X > x; // is equivilant to // X *const x; // not // X const *x; // That is to say, x is constant, but what it points to is not. // If you want the thing that is being pointed to to be constant, // use // TSRefCount< X > x; return *TSRefCountBase< baseType >::getPointer(); } baseType *operator ->() const { return TSRefCountBase< baseType >::getPointer(); } operator TSRefCount< baseType >() const { TSRefCount< baseType > result; assign(result, *this); } }; // Adapted from http://en.cppreference.com/w/cpp/utility/hash // Strange. I tried adding just one rule to cover the base // class, TSRefCountBase. That didn't work. When I tried to // instantiate a hash table for TSRefCount, 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< TSRefCount< T > > { typedef TSRefCountBase< 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< NCTSRefCount< T > > { typedef TSRefCountBase< T > argument_type; typedef std::size_t result_type; result_type operator()(argument_type const& p) const { return p.hash_code(); } }; } #endif