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.

Share the Post:
XDR solutions

The Benefits of Using XDR Solutions

Cybercriminals constantly adapt their strategies, developing newer, more powerful, and intelligent ways to attack your network. Since security professionals must innovate as well, more conventional endpoint detection solutions have evolved

AI is revolutionizing fraud detection

How AI is Revolutionizing Fraud Detection

Artificial intelligence – commonly known as AI – means a form of technology with multiple uses. As a result, it has become extremely valuable to a number of businesses across

AI innovation

Companies Leading AI Innovation in 2023

Artificial intelligence (AI) has been transforming industries and revolutionizing business operations. AI’s potential to enhance efficiency and productivity has become crucial to many businesses. As we move into 2023, several

data fivetran pricing

Fivetran Pricing Explained

One of the biggest trends of the 21st century is the massive surge in analytics. Analytics is the process of utilizing data to drive future decision-making. With so much of

kubernetes logging

Kubernetes Logging: What You Need to Know

Kubernetes from Google is one of the most popular open-source and free container management solutions made to make managing and deploying applications easier. It has a solid architecture that makes

ransomware cyber attack

Why Is Ransomware Such a Major Threat?

One of the most significant cyber threats faced by modern organizations is a ransomware attack. Ransomware attacks have grown in both sophistication and frequency over the past few years, forcing

data dictionary

Tools You Need to Make a Data Dictionary

Data dictionaries are crucial for organizations of all sizes that deal with large amounts of data. they are centralized repositories of all the data in organizations, including metadata such as