0
class Person2 {
    Person2(){};
    Person2(const Person& _){};
    ~Person2(){};
    int refcount;
public:
    //static method to create a Person2
    static Person2* create()
    {
        Person2* p = new Person2();
        p->refcount=1;
        return p;
    }

    void add_ref()
    {
        refcount++;
    }

    void release()
    {
        if(--refcount==0) delete this;
    }

};

Is this code is a good idea? Also in the main I wrote

int main() {
    auto p2 = Person2::create();
    p2->add_ref();
    p2->release();
    p2->release();
    p2->release();
    return 0;
}

I do understand that we force the client to create an object using create() api here in heap rather than in stack. Please help me to understand what is the use case of such codes and drawbacks if any?

4
  • 4
    What you have is a poor man's std::shared_ptr Commented Jun 6, 2020 at 4:46
  • @PaulMcKenzie - not really. shared_ptr manages the lifetime of another object in cooperation with other shared_ptrs, and the last shared_ptr, as it ceases to exist, destroys that managed object. This code is of an object that destroys itself based on the number of calls of add_ref() and release(). Too many calls of release() cause undefined behaviour, so this object relies on the code calling release() managing itself correctly. Commented Jun 6, 2020 at 5:20
  • 1
    Well, I said it was a "poor man's" shared_ptr. Commented Jun 6, 2020 at 5:21
  • 1
    Be aware of the C++ rule of five and read more about C++ Commented Jun 6, 2020 at 5:56

2 Answers 2

3

In isolation (i.e. since no information is provided about code that uses the Person2 class) this "use case" is one of a poorly implemented reference counted object, in which other code calls Person2::create() to create an instance, uses the add_ref() member to increment a reference count, and the release() to decrement that reference count (and presumably other operations on the object while it is alive).

When the release() member function is called more times than add_ref(), the object commits suicide by doing delete this.

For example;

 Person2 object = Person2::create();
 object->add_ref();
 // do operations on the pointed to object
 object->release();
 // do more operations on the pointed to object
 object->release();      //   object commits suicide here

The reason I say this is poorly implemented is that, in the preceding code, there is nothing preventing the caller misusing the object after it commits suicide. For example, there is nothing to prevent the above being modified to

 Person2 object = Person2::create();
 object->add_ref();
 // do operations on the pointed to object
 object->release();
 // do more operations on the pointed to object
 object->release();      //   object commits suicide here

 //   do yet more operations on the pointed to object without calling Person2::create()

 object->release();

The two additional lines cause undefined behaviour.

The only way for the calling code to prevent undefined behaviour is to explicitly ensure that it doesn't call object->release() too many times, and not dereference object (e.g. call non-static member functions, or access data members) after it has committed suicide. In other words, the calling code needs to separately keep track of the object's reference count as well.

So all that is being achieved by an instance of Person2 managing its own reference count is forcing code which uses the class Person2 to also separately manage a reference count for each object it uses (or some other explicit approach to ensure it never calls object->release() too many times).

Contrary to other comments and answer, this is not a "poor man's shared_ptr". A shared_ptr is an object that manages the lifetime of another object, and cooperates with other shared_ptr instances to ensure the managed object is destroyed correctly. This Person2 class does nothing of the sort.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks @Peter. Is there a way I can evidently see the UB?
By definition, no. Undefined behaviour just means that the C++ standard doesn't define or limit what happens. The behaviour can be anything between appearing to run completely correctly to (if the host hardware has such a capability) instantaneously boiling the programmer. In either of those circumstances, the programmer will perceive no evidence of undefined behaviour. Symptoms that are visible to the programmer, such as a program crash or reformatting a hard drive, are possible, but not guaranteed consequences of undefined behaviour.
There are multiple ways to express shared ownership/lifetime. A manually controlled ref counter within the object is one way of doing that. But in contrast to a shared_ptr, you need to make sure that in the given code flow the add_ref and release pairs are always called correctly. Due to that, it is an error-prone approach handling lifetime/ownership, similar to manually locking and unlocking a mutex and why lock_guard exists. It is different to shared_ptr, but not that far off, you still have a part in the code that performs the task of shared_ptr controlling the ref counter.
2

The behavior is exactly what you would expect, the current object (which must be on the free store) is first destroyed then released. If the object is not on the free store the destruction step will succeed but the free store deallocation will not. And the language won't help with this bit, it's something you need to ensure yourself (either using a factory or by some other means).

The use case is just as shown (and said, a poor man's unique_ptr), it is also needed when manually implementing windows COM objects (though there I would highly recommend using ATL or some other library to take care of some of the surprising intricacies).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.