官术网_书友最值得收藏!

Defaulted and deleted functions

In C++, classes have special members (constructors, destructors, and assignment operators) that may be either implemented by default by the compiler or supplied by the developer. However, the rules for what can be default implemented are a bit complicated and can lead to problems. On the other hand, developers sometimes want to prevent objects from being copied, moved, or constructed in a particular way. This is possible by implementing different tricks using these special members. The C++11 standard has simplified many of these by allowing functions to be deleted or defaulted in the manner we will see in the next section.

Getting started

For this recipe, you need to be familiar with the following concepts:

  • Special member functions (default constructor, destructor, copy constructor, move constructor, copy assignment operator, move assignment operator)
  • The copyable concept (a class features a copy constructor and copy assignment operator making it possible to create copies)
  • The moveable concept (a class features a move constructor and a move assignment operator making it possible to move objects)

With this in mind, let's learn how to define default and deleted special functions.

How to do it...

Use the following syntax to specify how functions should be handled:

  • To default a function, use =default instead of the function body. Only special class member functions that have defaults can be defaulted:
    struct foo
    {
      foo() = default;
    };
    
  • To delete a function, use =delete instead of the function body. Any function, including non-member functions, can be deleted:
    struct foo
    {
      foo(foo const &) = delete;
    };
    void func(int) = delete;
    

Use defaulted and deleted functions to achieve various design goals, such as the following examples:

  • To implement a class that is not copyable, and implicitly not movable, declare the copy constructor and the copy assignment operator as deleted:
    class foo_not_copyable
    {
    public:
      foo_not_copyable() = default;
      foo_not_copyable(foo_not_copyable const &) = delete;
      foo_not_copyable& operator=(foo_not_copyable const&) = delete;
    };
    
  • To implement a class that is not copyable, but is movable, declare the copy operations as deleted and explicitly implement the move operations (and provide any additional constructors that are needed):
    class data_wrapper
    {
      Data* data;
    public:
      data_wrapper(Data* d = nullptr) : data(d) {}
      ~data_wrapper() { delete data; }
      data_wrapper(data_wrapper const&) = delete;
      data_wrapper& operator=(data_wrapper const &) = delete;
      data_wrapper(data_wrapper&& other) :data(std::move(other.data))
      {
        other.data = nullptr;
      }
      data_wrapper& operator=(data_wrapper&& other)
      {
        if (this != std::addressof(other))
        {
          delete data;
          data = std::move(other.data);
          other.data = nullptr;
        }
        return *this;
      }
    };
    
  • To ensure a function is called only with objects of a specific type, and perhaps prevent type promotion, provide deleted overloads for the function (the following example with free functions can also be applied to any class member functions):
    template <typename T>
    void run(T val) = delete;
    void run(long val) {} // can only be called with long integers
    

How it works...

A class has several special members that can be implemented, by default, by the compiler. These are the default constructor, copy constructor, move constructor, copy assignment, move assignment, and destructor (for a discussion on move semantics, refer to the Implementing move semantics recipe in Chapter 9, Robustness and Performance). If you don't implement them, then the compiler does it so that instances of a class can be created, moved, copied, and destructed. However, if you explicitly provide one or more of these special methods, then the compiler will not generate the others according to the following rules:

  • If a user-defined constructor exists, the default constructor is not generated by default.
  • If a user-defined virtual destructor exists, the default constructor is not generated by default.
  • If a user-defined move constructor or move assignment operator exists, then the copy constructor and copy assignment operator are not generated by default.
  • If a user-defined copy constructor, move constructor, copy assignment operator, move assignment operator, or destructor exists, then the move constructor and move assignment operator are not generated by default.
  • If a user-defined copy constructor or destructor exists, then the copy assignment operator is generated by default.
  • If a user-defined copy assignment operator or destructor exists, then the copy constructor is generated by default.

Note that the last two rules in the preceding list are deprecated rules and may no longer be supported by your compiler.

Sometimes, developers need to provide empty implementations of these special members or hide them in order to prevent the instances of the class from being constructed in a specific manner. A typical example is a class that is not supposed to be copyable. The classical pattern for this is to provide a default constructor and hide the copy constructor and copy assignment operators. While this works, the explicitly defined default constructor ensures the class is no longer considered trivial and, therefore, a POD type. The modern alternative to this is using a deleted function, as shown in the preceding section.

When the compiler encounters =default in the definition of a function, it will provide the default implementation. The rules for special member functions mentioned earlier still apply. Functions can be declared =default outside the body of a class if and only if they are inlined:

class foo
{
public:
  foo() = default;
  inline foo& operator=(foo const &);
};
inline foo& foo::operator=(foo const &) = default;

The defaulted implementations have several benefits, including the following:

  • Can be more efficient than the explicit ones.
  • Non-defaulted implementations, even if they are empty, are considered non-trivial, and that affects the semantics of the type, which becomes non-trivial (and, therefore, non-POD).
  • Helps the user not write explicit default implementations. For instance, if a user-defined move constructor is present, then the copy constructor and the copy assignment operator are not provided by default by the compiler. However, you can still default explicitly and ask the compiler to provide them so that you don't have to do it manually.

When the compiler encounters the =delete in the definition of a function, it will prevent the calling of the function. However, the function is still considered during overload resolution, and only if the deleted function is the best match does the compiler generate an error. For example, by giving the previously defined overloads for the run() function, only calls with long integers are possible. Calls with arguments of any other type, including int, for which an automatic type promotion to long exists, will determine a deleted overload to be considered the best match and therefore the compiler will generate an error:

run(42);  // error, matches a deleted overload
run(42L); // OK, long integer arguments are allowed

Note that previously declared functions cannot be deleted as the =delete definition must be the first declaration in a translation unit:

void forward_declared_function();
// ...
void forward_declared_function() = delete; // error

The rule of thumb, also known as The Rule of Five, for class special member functions is that if you explicitly define any copy constructor, move constructor, copy assignment operator, move assignment operator, or destructor, then you must either explicitly define or default all of them.

The user-defined destructor, copy-constructor, and copy assignment operator are necessary because objects are constructed from copies in various situations (like passing parameters to functions). If they are not user-defined, they are provided by the compiler, but their default implementation may be wrong. If the class manages resources, then the default implementation does a shallow copy, meaning that it copies the value of the handle of the resource (such as a pointer to an object) and not the resource itself. In such cases, a user-defined implementation must do a deep copy that copies the resource, not the handle to it. The presence of the move constructor and move assignment operator are desirable in this case because they represent a performance improvement. Lacking these two is not an error but a missed optimization opportunity.

See also

  • Uniformly invoking anything callable to learn how to use std::invoke() to invoke any callable object with the provided arguments
主站蜘蛛池模板: 芷江| 永胜县| SHOW| 昌图县| 蒲城县| 七台河市| 寻乌县| 临夏市| 嫩江县| 武平县| 清远市| 宜川县| 甘南县| 三门县| 通州市| 响水县| 浦江县| 响水县| 兰西县| 灵璧县| 张家口市| 宽甸| 汉中市| 霍山县| 涞源县| 德钦县| 石城县| 佛学| 襄樊市| 富锦市| 岳池县| 永春县| 若羌县| 南城县| 高台县| 六安市| 都兰县| 胶州市| 左贡县| 繁峙县| 塔河县|