Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of public destructor when the constructor is private

I have seen code where the constructor has been declared as private while the destructor is public. What is the use of such a declaration? Is the destructor required to be public so that during inheritance the calls can be possible or is it a bug in the code?

The question might seem to be a bit short on information, but what I really want to know is if having a public destructor when the constructor is required to be private abides by the C++ rules?

like image 797
Sankalp Avatar asked Aug 31 '13 08:08

Sankalp


1 Answers

Short Answer

Creating a constructor as private but the destructor as public has many practical uses.

You can use this paradigm to:

  • Enforcing reference counting (See Hitesh Vaghani's example).
  • Implement the singleton pattern
  • Implement the factory pattern.

Long Answer

Above I hinted that you can use private constructors and destructors to implement several design patterns. Well, here's how...

Reference Counting

Using private destructor within an object lends itself to a reference counting system. This lets the developer have stronger control of an objects lifetime.

class MyReferenceObject
{
public:
    static MyReferenceObject* Create()
    {
        return new MyReferenceObject();
    }

    void retain()
    {
        m_ref_count++;
    }

    void release()
    {
        m_ref_count--;
        if (m_ref_count <= 0)
        {
            // Perform any resource/sub object cleanup.
            // Delete myself.
            delete this; // Dangerous example but demonstrates the principle.
        }
    }
private:

    int m_ref_count;

    MyReferenceObject()
    {
        m_ref_count = 1;
    }

    ~MyReferenceObject() { }

}

int main()
{
    new MyReferenceObject(); // Illegal.
    MyReferenceObject object; // Illegal, cannot be made on stack as destructor is private.

    MyReferenceObject* object = MyReferenceObject::Create(); // Creates a new instance of 'MyReferenceObject' with reference count.
    object->retain(); // Reference count of 2.
    object->release(); // Reference count of 1.
    object->release(); // Reference count of 0, object deletes itself from the heap.
}

This demonstrates how an object can manage itself and prevent developers from corrupting the memory system. Note that this is a dangerous example as MyReferenceObject deletes itself, see here for a list of things to consider when doing this.

Singleton

A major advantage to private constructors and destructors within a singleton class is that enforces the user to use it in only the manner that the code was design. A rogue singleton object can't be created (because it's enforced at compile time) and the user can't delete the singleton instance (again, enforced at compile time).

For example:

class MySingleton
{
public:
     MySingleton* Instance()
     {
        static MySingleton* instance = NULL;
        if (!instance)
        {
            instance = new MySingleton();
        }

        return instance;
     }
private:
    MySingleton() { }
    ~MySingleton() { } 
}

int main()
{
     new MySingleton(); // Illegal
     delete MySingleton::Instance(); // Illegal.
}

See how it is almost impossible for the code to be misused. The proper use of the MySingleton is enforce at compile time, thus ensuring that developers must use MySingleton as intended.

Factory

Using private constructors within the factory design pattern is an important mechanism to enforce the use of only the factory to create objects.

For example:

class MyFactoryObject
{
public:

protected:
    friend class MyFactory; // Allows the object factory to create instances of MyFactoryObject

    MyFactoryObject() {} // Can only be created by itself or a friend class (MyFactory).
}

class MyFactory
{
public:
    static MyFactoryObject* MakeObject()
    {

        // You can perform any MyFactoryObject specific initialisation here and it will carry through to wherever the factory method is invoked.
        return new MyFactoryObject();
    }
}

int main()
{
    new MyFactoryObject(); // Illegal.
    MyFactory::MakeObject(); // Legal, enforces the developer to make MyFactoryObject only through MyFactory.
}

This is powerful as it hides the creation of MyFactoryObject from the developer. You can use the factory method to perform any initilisation for MyFactoryObject (eg: setting a GUID, registering into a DB) and anywhere the factory method is used, that initilisation code will also take place.

Summary

This is just a few examples of how you can use private constructors and destructors to enforce the correct use of your API. If you want to get tricky, you can combine all these design patterns as well ;)

like image 88
matthewrdev Avatar answered Oct 13 '22 00:10

matthewrdev