The rule of three/five/zero

一个例子Chatbot:

#ifndef CHATBOT_H_
#define CHATBOT_H_

#include <wx/bitmap.h>
#include <string>

class GraphNode; // forward declaration
class ChatLogic; // forward declaration

class ChatBot
{
private:
    wxBitmap *_image; // avatar image
    GraphNode *_rootNode;
    ChatLogic *_chatLogic;

public:
    // constructors / destructors
    ChatBot();                     // constructor WITHOUT memory allocation
    ChatBot(std::string filename); // constructor WITH memory allocation
    ~ChatBot();

 
    ChatBot(const ChatBot &source); // copy constructor
    ChatBot &operator=(const ChatBot &source); // copy assignment operator
    ChatBot(ChatBot &&source); // move constructor
    ChatBot &operator=(ChatBot &&source); //  move assignment operator
 
};

#endif /* CHATBOT_H_ */
#include <iostream>
#include <random>
#include <algorithm>
#include <ctime>

#include "chatlogic.h"
#include "graphnode.h"
#include "graphedge.h"
#include "chatbot.h"

// constructor WITHOUT memory allocation
ChatBot::ChatBot()
{
    // invalidate data handles
    _image = nullptr;
    _chatLogic = nullptr;
    _rootNode = nullptr;
}

// constructor WITH memory allocation
ChatBot::ChatBot(std::string filename)
{
    std::cout << "ChatBot Constructor" << std::endl;
    
    // invalidate data handles
    _chatLogic = nullptr;
    _rootNode = nullptr;

    // load image into heap memory
    _image = new wxBitmap(filename, wxBITMAP_TYPE_PNG);
}

ChatBot::~ChatBot()
{
    std::cout << "ChatBot Destructor" << std::endl;

    // deallocate heap memory
    if(_image != NULL) // Attention: wxWidgets used NULL and not nullptr
    {
        delete _image;
        _image = NULL;
    }
}
ChatBot::ChatBot(const ChatBot &source) // 2 : copy constructor
{
    std::cout << "ChatBot Copy Constructor" << std::endl;
    _rootNode = source._rootNode;
    _chatLogic = source._chatLogic;
    _image = source._image;
}


ChatBot& ChatBot::operator=(const ChatBot &source) // 3 : copy assignment operator
{
    std::cout << "ChatBot Copy Assignment" << std::endl;
    if (this == &source)
        return *this;
    _rootNode = source._rootNode;
    _chatLogic = source._chatLogic;
    _image = source._image;
    return *this;
}

ChatBot::ChatBot(ChatBot &&source) // 4 : move constructor
{
    std::cout << "ChatBot Move Constructor " << this << std::endl;
    _image = source._image;
    _chatLogic = source._chatLogic;
    _chatLogic->SetChatbotHandle(this);
    _rootNode = source._rootNode;
    source._image = nullptr;
    source._chatLogic = nullptr;
    source._rootNode = nullptr;
}

ChatBot& ChatBot::operator=(ChatBot &&source) // 5 : move assignment operator
{
    std::cout << "ChatBot Move Assignment Operator " << this << std::endl;
    if (this == &source)
        return *this;

    delete _image;

    _image = source._image;
    _chatLogic = source._chatLogic;
    _chatLogic->SetChatbotHandle(this);
    _rootNode = source._rootNode;

    source._rootNode = nullptr;
    source._image = nullptr;
    source._chatLogic = nullptr;

    return *this;
}



}
  • Copy constructors/assignment:
    Copy constructors are used to initialize a class by making a copy of an object of the same class. Copy assignment is used to copy one class to another existing class. By default, C++ will provide a copy constructor and copy assignment operator if one is not explicitly provided. These compiler-provided functions do shallow copies, which may cause problems for classes that allocate dynamic memory. So classes that deal with dynamic memory should override these functions to do deep copies.
    所以这里的copy constuctor/assignment里对于_image的copy是shallow copy, 只是复制了指针,而没有复制指针指向的address上的数据
    You need to use a deep copy here.A deep copy copies all fields and makes copies of dynamically allocated memory pointed to by the fields.
    *_image = *source._image; // deep copy

浅拷贝导致的结果是可能在call dtor的时候两次destroy同一内存地址的数据
对于这类问题的解决办法一是自己定义deep copy in copy constructor/assignment, 也有人会直接禁用copy constructor/assignment, 于是我看到了这篇文章:
为什么很多人禁用拷贝(复制)构造函数

  • The rule of three/five/zero
    According to CppCoreGuidelines:

C.20: If you can avoid defining default operations, do

C.21: If you define or =delete any copy, move, or destructor function, define or =delete them all

Back to Basics: RAII and the Rule of Zero - Arthur O'Dwyer - CppCon 2019

推荐阅读更多精彩内容