Special function generation mechanism of C + + classes

Special function generation mechanism of C + + classes

rule

Refer to the instructions on Effective Morder C + +

  • Default constructor: generated only if the class does not contain a user declared constructor.
  • Destructor: generated by default. When the destructor of the base class is virtual, the default destructor of the derived class is virtual.
  • Copy constructor: generated only if the class does not contain a user declared copy constructor. If the class declares a move operation, the copy constructor will be defined as deleted.
  • Copy assignment operator: generated only if the class does not contain the copy assignment operator declared by the user. If the class declares a move operation, the copy assignment operator will be defined as deleted.
  • Move constructor and move assignment operator: generated only if the class does not contain user declared copy operation, move operation and destructor.

Example: A BUG

Because I am not familiar with the generation mechanism of destructors, it leads to a BUG.

First of all, there is no problem with the following code, because the data member m#, So the Widget is also a move only type by default; An STD:: pair < int, Widget > constructed by move only type can also be inserted into mm, because pair supports right value parameter construction (which can be constructed by move only Widget) and its own move constructor (which can be moved into unordered_map) by default:

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-RVYO7F1A-1631511324568)(... /... / library / Application Support / typora user images / image-20210913100235309. PNG)]

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-bISrO39b-1631511324570)(... /... / library / Application Support / typora user images / image-20210913100228439. PNG)]

class Widget {
public:
    Widget() = default;
//    ~Widget() = default;
private:
    std::thread m_; // Move type only
};

unordered_map<int, Widget> mm;
mm.insert({12, Widget()});

Then, I added a default destructor:

class Widget {
public:
    Widget() = default;
    ~Widget() = default;
private:
    std::thread m_; // Move type only
};

unordered_map<int, Widget> mm;
mm.insert({12, Widget()}); // error!

The error message is extremely long, and the core error is:

error: no matching function for call to 'std::unordered_map<int, Widget>::insert(<brace-enclosed initializer list>)'
   45 |     unordered_map<int, Widget> mm;

The structure of std::pair can be extracted separately to see clearer error messages:

// The code is as follows:
make_pair(12, Widget());

// Errors are reported as follows:
In template: no matching constructor for initialization of '__pair_type' (aka 'pair<int, Widget>')

Obviously, because the Widget's move constructor is implicitly deleted (it can neither copy nor move), it is impossible to construct an std::pair from the Widget parameter.

The solution is not to define a destructor or explicitly define a move constructor:

class Widget {
public:
    Widget() = default;
    Widget(Widget&&) = default;
    ~Widget() = default;
private:
    std::thread m_; // Move type only
};

unordered_map<int, Widget> mm;
mm.insert({12, Widget()});

Examples: std::mutex and std::thread

At the beginning of my experiment, I mistakenly recorded std::mutex as a shift only type and defined such a class:

class Widget {
public:
    Widget() = default;
private:
    std::mutex m_;
};

unordered_map<int, Widget> mm;
mm.insert({12, Widget()}); // error!

Widget s can't be copied and moved even when I don't add a destructor.

Look at the source code:

class mutex : private __mutex_base
{
  public:
    /* ... */
    mutex() noexcept = default;
    ~mutex() = default;

    mutex(const mutex&) = delete;
    mutex& operator=(const mutex&) = delete;
	  /* ... */
}

Obviously, because mutex defines its own default destructor and defines the copy constructor as deleted, its move constructor will also be implicitly deleted, so mutex can neither copy nor move.

Compare with std::thread source code:

class thread
{
  public:
  thread() noexcept = default;
  thread(const thread&) = delete;
  thread(thread&& __t) noexcept
  { 
    swap(__t);
  }
  ~thread()
  {
    if (joinable())
      std::terminate();
  }
}

Although std::thread defines the destructor and the deleted copy constructor, it explicitly defines the move constructor, which makes it move although it cannot copy.

Aside: why can't std::mutex be moved?

Generally speaking, std::mutex is usually called by multiple threads. If its location can be changed, how can all threads know where its new location is?

See stackoverflow for details: https://stackoverflow.com/questions/7557179/move-constructor-for-stdmutex

Tags: C++ Rust Cpp

Posted on Sat, 20 Nov 2021 20:57:29 -0500 by shatztal