C++拷贝构造函数在用法(非常详细)
在C++中,除需要使用构造函数直接创建一个新的对象外,有时还需要根据已经存在的某个对象创建一个它的副本,就像那只叫作多利的羊一样,我们希望根据一只羊创建出另一只一模一样的羊。
例如:
拷贝构造函数实际上是构造函数的“表亲”,在语法格式上,两者基本相似,拥有相同的函数名,只是拷贝构造函数的参数是该类对象的引用,而它所创建的对象是对作为参数的对象的一个复制,即为它的一个副本。
与构造函数相似,默认情况下,如果一个类没有显式地定义其拷贝构造函数,编译器会为其创建一个默认的拷贝构造函数。这个默认的拷贝函数通过内存拷贝的方式将旧有对象的内存空间中的数据拷贝到新对象的内存空间,以完成新对象的创建。
前面程序中的 Sheep 类没有定义拷贝构造函数,因此 shDolly 对象的创建就是通过这种默认的拷贝构造函数完成的。
在大多数情况下,默认版本的拷贝构造函数已经能够满足我们拷贝复制对象的需要,无须显式地定义拷贝构造函数。
然而,在某些特殊情况下,特别是类中有指针类型的成员变量时,以拷贝内存方式实现的默认拷贝构造函数只能复制指针成员变量的值,而不能复制指针所指向的内容。这样,新旧两个对象中不同的两个指针指向相同的内容,这显然是不合理的。
默认的拷贝构造函数无法正确地完成这类对象的拷贝。在这种情况下,就需要自己定义类的拷贝构造函数,以完成指针成员变量需要特殊处理的内容拷贝工作。
例如,有一个 Computer 类,它有一个指针类型的成员变量 m_pKeyboard,该指针指向一个独立的 Keyboard 对象。在这种情况下,默认的拷贝函数将无法正确地完成对象的拷贝,因为它只会赋值指针的值,而不会创建新的 Keyboard 对象。为了正确地复制这样的对象,我们需要定义 Computer 类的拷贝构造函数来完成特殊的复制工作:
首先,获得已有对象的指针类型成员变量,进而通过它获得它所指向的对象,然后创建一个副本,并将新对象中的相应指针类型的成员变量指向这个新创建的对象,例如:
我们自己定义的拷贝构造函数不仅能够正确地拷贝 Computer 类中的对象类型成员变量 m_strModel,还能够正确地完成指针类型成员变量 m_pKeyboard 的复制,从而实现 Computer 对象的完整复制。例如:
例如:
// 调用构造函数创建一个新对象 shMother Sheep shMother; // 对 shMother 进行一些操作 // 利用 shMother 对象创建一个一模一样的新对象 shDolly 作为其副本 Sheep shDolly(shMother);首先创建了一个 Sheep 类的新对象 shMother,然后对它进行了一些操作,如改变其成员变量等。接着,我们使用这个对象作为 Sheep 类的构造函数的参数,创建了一个与 shMother 对象一模一样的副本 shDolly 对象。我们将这种可以接受自身的某个对象作为参数,并创建一个新对象作为其副本的构造函数称为拷贝构造函数。
拷贝构造函数实际上是构造函数的“表亲”,在语法格式上,两者基本相似,拥有相同的函数名,只是拷贝构造函数的参数是该类对象的引用,而它所创建的对象是对作为参数的对象的一个复制,即为它的一个副本。
与构造函数相似,默认情况下,如果一个类没有显式地定义其拷贝构造函数,编译器会为其创建一个默认的拷贝构造函数。这个默认的拷贝函数通过内存拷贝的方式将旧有对象的内存空间中的数据拷贝到新对象的内存空间,以完成新对象的创建。
前面程序中的 Sheep 类没有定义拷贝构造函数,因此 shDolly 对象的创建就是通过这种默认的拷贝构造函数完成的。
在大多数情况下,默认版本的拷贝构造函数已经能够满足我们拷贝复制对象的需要,无须显式地定义拷贝构造函数。
然而,在某些特殊情况下,特别是类中有指针类型的成员变量时,以拷贝内存方式实现的默认拷贝构造函数只能复制指针成员变量的值,而不能复制指针所指向的内容。这样,新旧两个对象中不同的两个指针指向相同的内容,这显然是不合理的。
默认的拷贝构造函数无法正确地完成这类对象的拷贝。在这种情况下,就需要自己定义类的拷贝构造函数,以完成指针成员变量需要特殊处理的内容拷贝工作。
例如,有一个 Computer 类,它有一个指针类型的成员变量 m_pKeyboard,该指针指向一个独立的 Keyboard 对象。在这种情况下,默认的拷贝函数将无法正确地完成对象的拷贝,因为它只会赋值指针的值,而不会创建新的 Keyboard 对象。为了正确地复制这样的对象,我们需要定义 Computer 类的拷贝构造函数来完成特殊的复制工作:
// 键盘类,因为结构简单,我们使用 struct 来定义 struct Keyboard { // 键盘的型号 string m_strModel; }; // 定义了拷贝构造函数的计算机类 class Computer { public: // 默认构造函数 Computer() : m_pKeyboard(nullptr), m_strModel("") { } // 拷贝构造函数,参数是 const 修饰的 Computer 类的引用 Computer(const Computer& com) : m_strModel(com.m_strModel) { // 创建新对象,完成指针类型成员变量 m_pKeyboard 的复制 // 获得已有对象 com 的指针类型成员变量 m_pKeyboard Keyboard* pOldKeyboard = com.GetKeyboard(); // 以 pOldKeyboard 所指向的 Keyboard 对象为蓝本 // 创建一个新的 Keyboard 对象,并让 m_pKeyboard 指向这个对象 if (nullptr != pOldKeyboard) { // 这里 Keyboard 对象的复制使用的是 Keyboard 类的默认拷贝构造函数 m_pKeyboard = new Keyboard(*(pOldKeyboard)); } else { m_pKeyboard = nullptr; // 如果没有键盘 } } // 析构函数 // 对于对象类型的成员变量 m_strModel,会被自动销毁,无须在析构函数中进行处理 // 对于指针类型的成员变量 m_pKeyboard,则需要在析构函数中主动销毁 ~Computer() { delete m_pKeyboard; m_pKeyboard = nullptr; } // 成员函数,设置或获得键盘对象指针 void SetKeyboard(Keyboard* pKeyboard) { m_pKeyboard = pKeyboard; } Keyboard* GetKeyboard() const { return m_pKeyboard; } // 设置和获取计算机型号 void SetModel(string model) { m_strModel = model; } string GetModel() { return m_strModel; } private: // 指针类型的成员变量 Keyboard* m_pKeyboard = nullptr; // 对象类型的成员变量 string m_strModel = ""; };在这段代码中,我们为 Computer 类创建了一个自定义的拷贝构造函数。在这个拷贝构造函数中,对于对象类型的成员变量 m_strModel,我们直接使用初始化属性列表就完成了成员变量的拷贝。而对于指针类型的成员变量 m_pKeyboard 而言,它并不是拷贝这个指针的值本身,而是拷贝这个指针所指向的对象。
首先,获得已有对象的指针类型成员变量,进而通过它获得它所指向的对象,然后创建一个副本,并将新对象中的相应指针类型的成员变量指向这个新创建的对象,例如:
m_pKeyboard = new Keyboard(*(pOldKeyboard));其中,*(pOldKeyboard) 是获取源对象中指针变量所指向的对象,new Keyboard(…) 是创建该对象的一个副本,而 m_pKeyboard = new… 则是将新对象中的指针指向这个新创建的副本。这样就完成了指针类型成员变量的复制。
我们自己定义的拷贝构造函数不仅能够正确地拷贝 Computer 类中的对象类型成员变量 m_strModel,还能够正确地完成指针类型成员变量 m_pKeyboard 的复制,从而实现 Computer 对象的完整复制。例如:
// 包含断言所在的头文件 #include <assert.h> // ... // 创建一个 Computer 对象 oldcom Computer oldcom; // 设置计算机的型号 oldcom.SetModel("MAX-1"); // 创建 oldcom 的 Keyboard 对象并修改其属性 Keyboard keyboard; keyboard.m_strModel = "Microsoft-101"; // 将键盘组装到 oldcom 上 oldcom.SetKeyboard(&keyboard); // 以 oldcom 为蓝本,利用 Computer 类的拷贝构造函数创建新对象 newcom // 新的 newcom 对象是 oldcom 对象的一个副本 Computer newcom(oldcom); // 使用断言判断两个 Computer 对象是否相同 // 计算机型号应该相同 assert (newcom.GetModel() == oldcom.GetModel()); // 不同的 Computer 对象应该拥有不同的 Keyboard 对象 assert (newcom.GetKeyboard() != oldcom.GetKeyboard()); // 因为是复制,不同的 Keyboard 对象应该是相同的型号 assert (newcom.GetKeyboard()->m_strModel == oldcom.GetKeyboard()->m_strModel());