Five Ways to Improve Your C++ Programming with C++0x Defaulted and Deleted Functions

Introduction

The default constructor, the copy constructor, the assignment operator and the destructor are collectively known as the special member functions. By default, C++ programming implicitly declares (and often implicitly defines) the special member functions in every class. Although you can suppress an implicit declaration of special member function by declaring that member explicitly, this technique is too limited and dangerous. C++0x was recently furnished with a new feature that gives you tighter control over the implicit declarations and definitions of special member functions. Let’s see how this new feature can simplify five different programming tasks.

Generating a Default Constructor

If you declare a copy constructor or a constructor with parameters, the compiler will not generate a default constructor for that class. The lack of a default constructor means that you cannot create arrays, among the rest:

  class C
  {
  //...
  public:
   C(const C&);
  };

  C arr[5]; //error, C has no default ctor

  In C++03, the workaround is to define a dummy default constructor manually:

  class C
  {
  //...
  public:
   C() {} // inefficient
   C(const C&);
  };

  C arr[5]; //OK

A user-defined constructor is often less efficient than an implicitly-defined one (yes, even if your constructor does nothing). What you need is a mechanism for instructing the compiler to generate a default constructor for class C. That’s exactly what the =default specifier does:

  class C
  {
  //...
  public:
   C()=default;
   C(const C&);
  };
  C arr[5]; //OK

Due to the =default declaration, the compiler will implicitly define a default constructor for C as if the programmer hadn’t declared a copy constructor.

Disabling Copying

In C++03, you have to declare the copy constructor and assignment operator private if you wish to disable copying:

  class NoCopy
  {
  public:
   NoCopy();
  private:
   NoCopy(const NoCopy& );
   NoCopy& operator=(const NoCopy &);
  };

  NoCopy nc;
  NoCopy nc2(nc); //error: copy ctor is inaccessible

Seemingly, the private declarations get the job done. However, there are several security hazards here. First, other member functions of NoCopy are still able to invoke the private copy constructor and assignment operator because a class’ member function can always access every other member of the same class. Second, leaving the copy constructor and assignment operator declared but not defined could annoy some linkers, even if your program never invokes these member functions. To pacify those linkers, you’ll have to provide dummy definitions that might compromise security — once you define a member function, another member function might invoke it.

The C++0x =delete specifier solves this problem neatly:

  class NoCopy
  {
  //...
  public:
   NoCopy(const NoCopy& )=delete;
   NoCopy operator=(const NoCopy &)=delete;
  };

  NoCopy nc, nc2;
  NoCopy nc3(nc); //error: copy ctor is deleted
  nc=nc2; //error: operator= is deleted

Notice that you don’t have to declare the deleted member functions private. With respect to security, a deleted special member function offers two advantages: defining a deleted function will cause a compilation error. More importantly, other member functions cannot call a deleted member function.

Free-Store Objects

Some frameworks are picky with respect to the storage type that they require. For example, smartphones’ operating systems let you instantiate objects using new exclusively. To restrict certain classes to free-store allocation, declare their destructors private, and define a static member function that releases the object’s resources:

  class S //C++03
  {
  public:
   static void destroy(S*); //release resources
  private:
  ~S();
  };
  S s; //error: destructor is inaccessible
  static S s2; //error: destructor is inaccessible

  S* p= new S; //OK
  S::destroy(p);

Here again, declaring the destructor private without a definition opens the door to all sorts of security risks and bugs. By contrast, declaring the destructor =delete is safe:

  class S //C++0x
  {
  public:
   static void destroy(S*);
   ~S()=delete;
  };
  S s; //error: destructor is deleted
  static S s2; //error: destructor is deleted

  S* p= new S; //OK
  S::destroy(p);

Virtual Destructors

Another destructor-related dilemma occurs when you have a hierarchy of classes. Recall that a base class destructor must be virtual. To ensure that, you have to declare the destructor explicitly:

  struct Interface
  {
  virtual ~ Interface();
  //...
  };

Here’s the snag: if you declare the destructor explicitly, the compiler will not generate a definition for it even if it’s trivial. Therefore, you’ll have to define it manually. As explained earlier, a manual definition of a special member function is less efficient than a compiler-generated one. The solution… Declare the virtual destructor =default:

  struct Interface
  {
  virtual ~ Interface()=default;
  //...
  };

Move Semantics

In addition to the four special member functions of C++03, C++0x introduces two new special member functions: a move constructor and a move assignment operator. They have the following canonical signatures:

  X(X&&); //move constructor
  X& operator= (X&&); //move assignment operator

At the time of writing, the C++0x FCD allows compilers to define a move constructor implicitly under certain conditions. However, the FCD is about to change. Implicitly-defined move constructors and move assignment operators are likely to be removed from the FCD because they are too dangerous. Instead, if you want the compiler to define a move constructor implicitly, declare it =default. A defaulted move constructor will apply std::move() to every data member of its object, or if a data member has a move constructor, the member’s move constructor will be called instead. The same is true for a move assignment operator:

  class Vector
  {
  public:
   Vector (Vector &&)=default;
   Vector& operator=(Vector&&)=default;
  //...
  };  

If you wish to disable move semantics, declare the move constructor and the move assignment operator =delete as follows:

  class BigInteger
  {
  public:
    //disable move semantics:
   BigInteger(BigInteger&&)=delete;  
   BigInteger& operator=(BigInteger&&)=delete;  

    //enable copy semantics:
   BigInteger(BigInteger&);
   BigInteger& operator= (BigInteger&);

   BigInteger()=default; //generate a default constructor
  };

Conclusion

The =default and =delete specifiers allow you to override the default rules of C++ programming with respect to the implicit declarations and definition of the special member functions. These two specifiers reuse existing C++ keywords: default and delete. The =specifier; syntax was inspired by the pure virtual specifier =0; that you often see in base classes. Starting with version 4.1, GCC supports this feature, as does Edison Design Group’s eccp compiler.

Related Articles

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read