views:

128

answers:

3

Most common re-usable reference counted objects use private inheritance to implement re-use. I'm not a huge fan of private inheritance, and I'm curious if this is an acceptable way of handling things:

class ReferenceCounter {
    std::size_t * referenceCount;
public:
    ReferenceCounter()
        : referenceCount(NULL) {};
    ReferenceCounter(ReferenceCounter& other)
        : referenceCount(other.referenceCount) {
            if (!referenceCount) {
                referenceCount = new std::size_t(1);
                other.referenceCount = referenceCount;
            } else {
                ++(*referenceCount);
            }
    };
    ReferenceCounter& operator=(const ReferenceCounter& other) {
            ReferenceCounter temp(other);
            swap(temp);
            return *this;
    };
    void swap(ReferenceCounter& other) {
        std::swap(referenceCount, other.referenceCount);
    };
    ~ReferenceCounter() {
        if (referenceCount) {
            if (!*referenceCount)
                delete referenceCount;
            else
                --(*referenceCount);

        }
    };
    operator bool() const {
        return referenceCount && (*referenceCount != 0);
    };
};

class SomeClientClass {
    HANDLE someHandleThingy;
    ReferenceCounter objectsStillActive;
public:
    SomeClientClass() {
        someHandleThingy = RegCreateKeyEx(...);
    }
    ~SomeClientClass() {
        if (objectsStillActive)
            return;
        RegCloseKey(someHandleThingy);
    };
};

or are there subtle problems with this I'm not seeing?

EDIT
I'm not super duper concerned with this particular implementation (it probably has bugs -- I'm going to spend some time looking at shared_ptr's innards before using something like this in production code) -- I'm just concerned if in general there is a specific reason reusable reference counting goodies always seem to be implemented using inheritance rather than composition.

+4  A: 

You have to remember to copy the counter when copying the handle. You might prefer not to pass operating system types into templates, but I think safety here requires inheritance. (Not inheritance from HANDLE, though.)

HANDLE might also be something of a special case because it's POD. Essentially you have a pointer of type besides T*.

I see the motivation that you want something besides delete to happen when the count goes to zero. An adaptation of smart_ptr would probably work, and you might not be that far from just that.

Potatoswatter
If that happens, then A. ReferenceCount objects were copied (because referenceCount is not a NULL pointer), and B. referenceCount is zero indicating that we are destroying the last ReferenceCount in a group. So the size_t pointer needs to be deallocated.
Billy ONeal
@Billy: But now that you've erased the counter, you can't read it to determine whether to delete the controlled object. The only thing here that seems to be reference counted is the counter itself. I guess I'm missing some point of interface.
Potatoswatter
If the last ReferenceCount object is being destroyed nobody's ever going to have access to the counter again anyway.
Billy ONeal
@Potatoswatter: Consider a wrapper class around Win32's RegOpenKeyEx function. RegCloseKeyEx needs to be called once on the handle when the user is done with it. If you just put a destructor in that RegCloseKey's the handle, you will end up destroying the handle while copies of your wrapper class might still be using that handle. With this bit, you can ask the ReferenceCount object if it's the last remaining ReferenceCount object and call RegCloseKey then. (P.S.: Did you scroll down the code panel with my example in it to `SomeClientClass`?)
Billy ONeal
@Billy: I see, thanks, answer updated. Suggest you write `if (objectsStillActive) DisposeHandle( someHandleThingy );` to make it crystal clear what is being managed.
Potatoswatter
@Potatoswatter: 1. SomeClientClass created. 2. SomeClientClass copied (Reference count now 1). 3. Copy destroyed (reference count now 0) 4. Original destroyed (`delete` called on reference counting pointer).
Billy ONeal
"Also, you have to remember to copy the counter when copying the handle. You might prefer not to pass operating system types into templates, but I think safety here requires inheritance." <-- Exactly the kind of thing I'm asking about. +1
Billy ONeal
@Potatoswatter: Ah -- I see the bug now. Fixed it in my answer.
Billy ONeal
A: 

I don't think this has any merit. Reference counting only makes sense for shared objects. The goal is to save on heap allocations and/or copying of these among other things. You, in fact, implemented sort of copy counter. But even that is not useful since it does not provide any interface to query the counter value. Might I suggest revisiting boost::intrusive?

Nikolai N Fetissov
That interface is `operator bool`, which returns whether the ReferenceCount object is the last remaining `ReferenceCount` object. Boost intrusive still requires a heap allocation which I'd like to avoid when copying is not involved.
Billy ONeal
A: 

You are in fact implementing reference counting for the HANDLE outside of the HANDLE class... which is damn close to a shared_ptr.

Using composition for implementing reference counting is fine, though it would be better if your reference counting object ReferenceCounter was to own the class HANDLE instance... Much safer in usage, and cheaper to reuse since you only implement the deletion routine once instead of doing it in all of your constructors (arg).

The single valid reason for using private inheritance is Empty Base Optimization, all the other cases can be dealt with composition which is much better in terms of coupling, so it's unlikely that they have good reasons to do so, more probably they did it out of misguidance or laziness.

Matthieu M.
"Much safer in usage, and cheaper to reuse since you only implement the deletion routine once instead of doing it in all of your constructors (arg)." <-- Any client using this will be the sole manager of the HANDLE object. Any constructor of such a client class will be a simple frontend that calls the WindowsAPI function and stores the handle in the RAII class. The EBO is not useful here because the reference counting class requires the sizeof a pointer to store anyway.
Billy ONeal