C++中拷贝构造函数和赋值操作符(Copy constructor & Assignment operator)

在C++中,经常会将对象进行赋值或拷贝,在C++中,在程序员没有手动编写相关内容时,系统会自动创建拷贝构造函数和复制操作符内容,对于值类型数据使用系统默认的函数可以正常运行,但是对于类类型、指针类型,系统自动自动创建的操作函数无法将类类型、指针类型创建副本,而是两个类对象同时持有一个拷贝副本,改变一个类对象数据,会造成另个一类对象数据改变,造成隐性bug。

在编写拷贝构造函数和赋值操作符时注意事项:

  • 首先调用基类的构造函数或赋值操作符,对基类数据进行拷贝。
  • 拷贝构造方法是构造方法的一种,初始化变量,所以不需要对指针类型字段进行空指针判别。
  • 对当前类的字段按照在“.h”文件中声明的顺序进行赋值,所有字段都要赋值。
  • 赋值操作符对指针类型字段先进行空指针判别,进行delete后再进行赋值,预防内存泄漏问题。

数据类:

.h

#ifndef DATA_CLASS_H
#define DATA_CLASS_H

#include <QObject>

/**
 * @copyright  2008-2023
 * @date       2023-07-14
 * @author     qiaowei
 * @version    1.0
 * @brief      数据类。有2个变量,分别是QString,int类型。
 * @history    None
 * @function   None
 * @field      None
*/
class DataClass : public QObject
{
    Q_OBJECT

public:
    explicit DataClass(QObject *parent = nullptr);

    /**
    * @author   qiao wei
    * @version  1.0
    * @brief    Copy constructor.
    * @param    value 拷贝的变量。
    * @return   None
    */
    DataClass(DataClass& value);

    virtual ~DataClass();

    DataClass& operator =(const DataClass& value);

public:
    QString name_;
    int level_;
};

#endif // DATA_CLASS_H

.cpp

#include <QtDebug>

#include "data-class.h"

DataClass::DataClass(QObject *parent)
    : QObject{parent}
    , name_{""}
    , level_{0}
{}

DataClass::DataClass(DataClass& value)
{
    // QObject's copy constructor and assignment operator are declared in a private section.
    if ((nullptr != &value) && (this != &value)) {
        this->name_ = value.name_;
        this->level_ = value.level_;
    }
}

DataClass::~DataClass()
{}

DataClass &DataClass::operator =(const DataClass &value)
{
    // QObject's copy constructor and assignment operator are declared in a private section.
    if (this == &value) {
        return *this;
    }

    // 赋值操作符要对指针字段先进行delete,使用nullprt赋值。保证不会出现内存泄漏。
    this->name_ = value.name_;
    this->level_ = value.level_;

    return *this;
}

类中的两个字段分别为QString、int类型。作为基础类型,也可以不用重写拷贝构造函数和赋值操作符。

父类BaseClass有2个字段,分别是DataClass指针data_,QString类型local_,因为字段data_为类指针,所有BaseClass必须重写拷贝构造函数和赋值操作符。

注意事项:

  • 拷贝构造函数是构造函数的一种,不需要对指针字段进行判断,不需要delete避免内存泄漏情况。
  • 赋值操作符是对已经创建的两个字段进行赋值,因为两个变量都已经new过,只是其中字段的赋值,所以要对指针字段进行判断,需要delete已经new的指针字段,避免内存泄漏情况。
  • 注意拷贝构造函数,赋值操作符中的字段处理顺序。建议按照构造函数中的字段处理顺序,先基类,后字段,字段按照类声明的顺序赋值。

.h文件:

#ifndef BASECLASS_H
#define BASECLASS_H

#include <QObject>

#include "data-class.h"

/**
 * @copyright  2008-2023
 * @date       2023-07-07
 * @author     qiaowei
 * @version    1.0
 * @brief      None
 * @history    None
 * @function   None
 * @field      None
*/
class BaseClass : public QObject
{
    Q_OBJECT

public:
    /**
    * @author   qiao wei
    * @contact  [email protected]
    * @version  1.0
    * @other    Used/Unused
    * @brief    Constructor。构造方法不能调用virtual方法。
    * @param    None
    * @return   None
    */
    explicit BaseClass(QObject *parent = nullptr);

    /**
    * @author   qiao wei
    * @contact  [email protected]
    * @version  1.0
    * @other    Used/Unused
    * @brief    Copy constructor。需要调用拷贝构造方法时不会调用其它的构造方法。同构造方法不能调用
    *            virtual方法。
    * @param    value 要拷贝的变量。
    * @return   None
    */
    BaseClass(const BaseClass& value);

    /**
    * @author   qiao wei
    * @contact  [email protected]
    * @version  1.0
    * @other    Used/Unused
    * @brief    Destructor.
    * @param    None
    * @return   None
    */
    virtual ~BaseClass();

    /**
    * @author   qiao wei
    * @contact  [email protected]
    * @version  1.0
    * @other    Used/Unused
    * @brief    Assignment Operator.
    * @param    value 赋值的变量。
    * @return   None
    */
    BaseClass& operator =(const BaseClass& value);

public:
    /**
    * @Date    2023-07-07
    * @Author  qiaowei
    * @Contact [email protected]
    * @Version 1.0
    * @Brief   指针字段,类类型数据。
    */
    DataClass* data_;

    /**
    * @Date    2023-07-07
    * @Author  qiaowei
    * @Contact [email protected]
    * @Version 1.0
    * @Brief   字段。
    */
    QString local_;
};

#endif // BASECLASS_H

.cpp文件:

#include <QtDebug>

#include "base-class.h"

BaseClass::BaseClass(QObject* parent)
    : QObject{parent}
    , data_{new DataClass{}}
    , local_{"local address"}
{}

BaseClass::BaseClass(const BaseClass& value)
{
    if ((nullptr != &value) && (this != &value)){
        /**
         * 注意事项:
         * 调用父类拷贝构造函数,因为QObjet的构造函数为private,故不可调用。
         * 拷贝构造函数是构造函数的一种,所有字段都是首次调用初始化,不需要对指针字段进行判断,进行delete避免内存泄漏
         *  情况。
        */

        // 直接对指针字段进行new。
        data_ = new DataClass(*(value.data_));
        local_ = value.local_;
    }
}

BaseClass::~BaseClass()
{
    // local_字段是基本类型,无需析构。在析构方法中只对指针字段进行处理,避免memory leak。
    if (nullptr != data_) {
        delete data_;
        data_ = nullptr;
    }
}

BaseClass &BaseClass::operator =(const BaseClass& value)
{
    // value就是当前字段本身,无需赋值,直接返回。
    if (this == &value) {
        return *this;
    }

    // Avoid memory leak.
    if (nullptr != data_) {
        delete data_;
        data_ = nullptr;
    }

    // 按照字段在类中的声明顺序,依次赋值。
    data_ = new DataClass(*(value.data_));
    local_ = value.local_;

    return *this;
}

运行测试:

#include <QApplication>
#include <QtDebug>

#include "deprived-test/base_class.h"
#include "deprived-test/data_class.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    BaseClass* base = new BaseClass(nullptr);
    qDebug()<< base->data_->name_ << endl;
    qDebug()<< base->data_->level_ << endl;

    BaseClass base01(*base);

    base01.data_->name_ = "test";
    qDebug()<< base->data_->name_ << endl;
    qDebug()<< base01.data_->name_ <<endl;


    return a.exec();
}

测试结果:

"" 

0 

"" 

"test" 

可以看到base01由base赋值,随后对base01的字段进行了修改,打印两者的字段,发现base的字段没有跟随base01的值而改变。所以指针字段分离。

DeprivedClass是BaseClass的子类,在拷贝构造方法,赋值操作符中对父类的相关操作进行调用。

.h

#ifndef DEPRIVEDCLASS_H
#define DEPRIVEDCLASS_H

#include <QObject>

#include "base-class.h"

class DeprivedClass : public BaseClass
{
    Q_OBJECT

public:
    explicit DeprivedClass(QObject *parent = nullptr);

    /**
    * @author   qiao wei
    * @contact  [email protected]
    * @version  1.0
    * @other    Used/Unused
    * @brief    Copy constructor.
    * @param    value DeprivedClass reference.
    * @return   None
    */
    DeprivedClass(const DeprivedClass& value);

    virtual ~DeprivedClass();

    DeprivedClass& operator =(const DeprivedClass& value);

public:
    DataClass* deprived_data_;
};

#endif // DEPRIVEDCLASS_H

.cpp

#include "deprived-class.h"

DeprivedClass::DeprivedClass(QObject *parent)
    : BaseClass{parent}
    , deprived_data_{new DataClass{}}
{}

DeprivedClass::DeprivedClass(const DeprivedClass &value)
{
    if ((nullptr != &value) && (this != &value)) {
        // Call copy constructor of base class.
        BaseClass::BaseClass(value);

        deprived_data_ = new DataClass(*(value.deprived_data_));
    }
}

DeprivedClass::~DeprivedClass()
{
    if (nullptr != deprived_data_) {
        delete deprived_data_;
        deprived_data_ = nullptr;
    }

    // Implicit call destructor of base class.
}

DeprivedClass &DeprivedClass::operator =(const DeprivedClass &value)
{
    if (this == &value) {
        return *this;
    }

    if (nullptr != &value) {
        // Call assignment operator of base class.
        BaseClass::operator =(value);

        // Avoid memory leak.
        if (nullptr != deprived_data_) {
            delete deprived_data_;
            deprived_data_ = nullptr;
        }

        // 对DeprivedClass中的字段进行赋值。
        deprived_data_ = new DataClass(*(value.deprived_data_));
    }

    return *this;
}

测试运行代码:

DeprivedClass* d01 = new DeprivedClass{};
    DeprivedClass d02(*d01);
    d02.deprived_data_->name_ = "aaa";
    DeprivedClass* d03 = new DeprivedClass{};
    *d03 = d02;

    qDebug()<< d03->deprived_data_->name_ << endl;
    qDebug()<< d03->deprived_data_ << endl;
    qDebug()<< &(d02.deprived_data_) << endl;

运行结果:

"aaa" 

DataClass(0x1caeaf4f760) 

0x1000ff5e0

猜你喜欢

转载自blog.csdn.net/weiweiqiao/article/details/131504237