Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Defaulted and Deleted Functions, Part II

Last updated Jan 1, 2003.

In the first part of this series I demonstrated the usage of defaulted functions. In this part, which seals this series, I will show how to declare deleted functions and show in which cases they can be useful.

Defaulted Functions: A Recap

Before we delve into the functionality of deleted functions, here’s a quick reminder of defaulted functions, in case you have forgotten.

Defaulted functions borrow the syntactic form of pure virtual functions. Yet unlike pure virtual functions that look like this:

class A
{
public:
 virtual void func()=0; 
};

The =default; part indicates that the compiler should generate the default implementation for the function in question:

struct A
{
 A()=default;
 virtual ~A()=default;
};

Defaulted functions solve two problems: they are more efficient than manual implementations, and they free the programmer from the burden of defining those functions manually.

Unlike user-declared member functions, which are never trivial, a defaulted function can be trivial in some cases. An inline and explicitly defaulted definition is trivial if and only if the implicit definition would have been trivial as well. Consider the destructor of struct A. It’s default-defined and implicitly inline. However, it’s not trivial because this destructor is different from the implicit destructor that compiler would have generated for this class (the implicit destructor wouldn’t have been virtual). In contrast, the default-defined constructor is trivial because if you removed its declaration from A, the compiler would implicitly declare a trivial default constructor.

Deleted Functions

The opposite of defaulting a function is deleting a function. The definition form:

int func()=delete; 

indicates that the function may not be used. However, all lookup and overload resolution occur before the function is conceptually deleted. In other words, it’s the definition that is deleted, not the symbol. In particular, overloads that resolve to a deleted definition are ill-formed.

Deleting a function is useful in two cases:

  • Deleting definitions of functions that are available by default, thus making their usage a compile-time error.
  • Preventing problematic conversions by deleting the definitions of undesirable conversions.

A classic example of using deleted function is preventing object copying. C++ automatically declares a copy constructor and an assignment operator for classes that don’t explicitly declare these functions. If you want to disable copying, simply declare these two special member functions as deleted:

struct B
{
  B & operator =( const B& ) = delete;
  B( const B& ) = delete;
};

Because a declaration of any constructor disables the default constructor, a programmer may add it back by defaulting it:

struct B
{
  B & operator =( const B& ) = delete;
  B( const B& ) = delete;
  B() =default;
};

B b;//ok, using default-defined default constructor
B b2=b; //error. Copy ctor is deleted
b=B(); //error, assignment operator is deleted

The deleted definition of a function must be its first declaration. Additionally, deleted functions are trivial. Therefore, B is a POD-type because its defaulted constructor is trivial, and so are its two deleted member functions.

Deleting the definition of a class-specific operator new will prevent allocation of such objects on the free store because new expressions involving that class will be ill-formed. Objects of type B can only have static or automatic storage, though:

struct B 
{
  void * operator new( std::size_t ) = delete;
};
B * p = new B; //error
B b; //OK
static B b; //OK

Deleting the definition of a destructor will require allocation on the free-store because static and automatic objects implicitly invoke the destructor:

struct C
{
  ~C()= delete; //prevent automatic and static objects
};

However, this technique is less useful than it may seem because it prevents delete expressions, too. Singleton objects can use it, though.

Preventing Undesirable Conversions

Deleted functions enable you to disable undesirable conversions. For example, the following constructor takes an argument that must be of type long long. If users pass long and int as arguments instead, the behavior is undefined. To prevent this, the programmer can delete the undesirable overload:

struct D
{
  D( long long ); // initialization requires long long
  D( long ) = delete; // any smaller integral type will cause an error
};

Henceforth, any attempt to pass an initializer that is smaller than long long will be trapped by the deleted constructor declaration, causing a compilation error:

D d(100); //compilation error, 100 is int
D d2(100LL); //OK

Conclusions

Personally, I like the defaulted and deleted functions proposal. The author has studied similar proposals and came up with an elegant, intuitive and non-intrusive solution to a collection of related problems. The reuse of existing keywords (default and delete) merged with the same syntactic form of a pure virtual specifier is truly praiseworthy. This way, the potential problems associated with adding new keywords are elegantly avoided but the new syntax is distinct enough to avoid any confusion between the traditional meanings of delete and default, as opposed to the new concepts of defaulted and deleted functions. Finally, the proposal is properly balanced -- supporting defaulted functions is great, but it can’t be a perfect solution without an opposite mechanism for deleting undesirable functions.