devxlogo

Imitate Sealed Classes and Disable Object Copying

Imitate Sealed Classes and Disable Object Copying

he keywords final and sealed in Java and C# respectively ensure that a certain class shall not be subclassed. C++ doesn’t have a similar keyword. Naive programmers are often tempted to derive from classes such as std::string and std::vector, which clearly weren’t meant to be used as base classes. These illicit derivations can cause undefined behavior. Find out how to implement a compile-time constraint that blocks subclassing&#151plus, a technique for blocking object copying.


How can you disable inheritance from a class that isn’t meant to be derived from? How do you prevent the copying of objects that aren’t meant to be copied?


Restrict the access of your special member functions.

Presenting the Problem
The Standard Template Library isn’t an object-oriented framework. Rather, it’s an object-based paradigm, whereby a typical class, say std::string, is an autonomous and complete entity. Implementation-wise, an object-based design is characterized by the following:

  • The class has a non-virtual destructor
  • The class doesn’t have any virtual member functions
  • The class doesn’t have any base classes
  • The class doesn’t contain any protected members

These telltale properties indicates that a certain class shouldn’t be derived from. And yet, the compiler won’t stop programmers from doing this. This is probably why many programmers, not fully aware of the risks, still attempt to derive from std::string, extending its functionality by adding a const char * conversion operator, or a trim() member function, etc. Trouble begins when you have a pointer to std::string that is bound to a derived object. When the said pointer is deleted, the derived subobject isn’t properly destroyed, causing undefined behavior. In the case of Standard Library containers, there’s not much that you can do to enforce the non-inheritable constraint. However, if you’re designing an application or a library that has non-inheritable classes, you can disable further derivation restricting their constructor(s) access.

Use Private Constructors
To enforce the non-inheritable constraint for a given class, declare at least one constructor explicitly as a private member (otherwise, the compiler will implicitly it as public). Now, any attempt to derive from this class will cause a compilation error:

class noninheritable{private: //all ctors are private to disable subclassing  explicit noninheritable(int n);  explicit noninheritable(const char *);};class derived: public noninheritable{public: derived(): noninheritable(0){} //error:                                 //ctor is inaccessible};

Instantiation and Destruction
While making the constructors private disables subclassing, it raises a new problem: how do you instantiate objects of type noninheritable? Consider:

noninheritable obj(1); //error, can't access private ctornoninheritable * p = new noninheritable("abc"); //dittostatic noninheritable sobj; //ditto

To solve this problem, add to noninheritable a static member function called create() that returns a pointer to a dynamically allocated noninheritable object. If the class has multiple constructors, you will need to overload create() accordingly:

class noninheritable{private:  explicit noninheritable(int n);  explicit noninheritable(const char *);public:  static noninheritable * create(int arg)   {   return new noninheritable(arg);  }  static noninheritable * create(const char* arg)   {   return new noninheritable(arg);  }};

To instantiate an object, call the matching overloaded version of create():

noninheritable * pstr = noninheritable::create("abc");noninheritable * pi = noninheritable::create(5);

This works because create() has unrestricted access to all members of its class, including private ones. To destroy the object, simply use delete as usual:

delete pstr;delete pi;

create() can allocate only one object at a time. To enable the instantiation of arrays (if necessary), define additional static member functions. Don’t be tempted to add an extra argument to the existing versions of create() as this could lead to confusion. Instead, give the array version a descriptive name such as create_array(). Remember, also, that the said class must have a default constructor to allow the creation of arrays.

Disabling Object Copying
C++ automatically declares a public copy constructor and a public assignment operator for a class that doesn’t declare these members explicitly. Yet in some cases, copying an object of a certain class is undesirable. File stream objects are a good example of this. If fstream objects were copyable, a program might accidentally have two objects accessing the same file simultaneously. Fortunately, the designers of the library prevented this from happening by disabling object copying. You can adopt the same technique in other classes as well. To disable copying, declare the copy constructor and the assignment operator as private. You don’t need to define these private member functions though. The explicit private declarations ensure that the compiler doesn’t declare them implicitly as public. Here’s an example:

class nocopy{private: //block object copying nocopy(const nocopy &); nocopy& operator=(const nocopy &);public: //enable instantiation nocopy() {} ~nocopy() {}};nocopy nc, nc2; //OK, using public default ctor & dtornocopy nc3(nc);//error, copy ctor is inaccssiblenc2=nc;//error, operator= is inaccessible

If you have several classes that need to enforce the non-copyable constraint, you may use Boost’s noncopyable class as a private base class for every class whose objects must not be copied. boost::noncopyable doesn’t have any data members. As such, it shouldn’t increase the size of classes derived from it. This is known as the empty base class optimization.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist