C++中要完整拷贝对象

简介: C++中要完整拷贝对象

1.问题的引入


在C++中拷贝函数有两类:拷贝构造函数和拷贝赋值运算符。在文章C++类中默认生成的函数中我提到过,如果我们在类中没有声明拷贝函数,C++中的编译器会默认自动为你生成。如果我们在类中自己声明了拷贝函数,意思就是告诉编译器你并不喜欢它默认生成的拷贝函数。当编译器感觉到自己被"冒犯"时,它就会对你进行"报复",即在你实现的代码出错时,并不会报错来通知你。如下例所示:


1void logCall(const std::string &funcName);
 2
 3class Customer
 4{
 5public:
 6    // ...
 7    Customer(const Customer &rhs);            // 拷贝构造函数
 8    Customer &operator=(const Customer &rhs); // 拷贝赋值运算符函数
 9private:
10    std::string name;
11};
12
13Customer::Customer(const Customer &rhs) : name(rhs.name)
14{
15    logCall("Customer类的拷贝构造函数");
16}
17
18Customer &Customer::operator=(const Customer &rhs)
19{
20    logCall("Customer类的拷贝赋值运算符函数");
21    name = rhs.name;
22    return *this;
23}


上面的Customer类中,使用的是自定义的拷贝函数。出现的问题:当在类中加入一个新的成员变量lastTransaction时,拷贝函数执行的是局部拷贝。如下例所示:


1void logCall(const std::string &funcName);
 2
 3class Date{
 4    // ...
 5};
 6class Customer
 7{
 8public:
 9    // ...
10    Customer(const Customer &rhs);            // 拷贝构造函数
11    Customer &operator=(const Customer &rhs); // 拷贝赋值运算符函数
12private:
13    std::string name;
14    Date lastTransaction;   // 新加入的成员变量
15};


上例中,拷贝函数的确复制了成员变量name,但是并没有复制新增加的成员变量lastTransaction。大多数编译器对此种情况也不会报错,这是编译器对你因不使用它为你默认生成的拷贝函数,作出的复仇行为。解决方法:如果你为类添加了一个成员变量,你必须同时修改你的拷贝函数。


2.隐藏在继承中的局部拷贝现象


局部拷贝现象更可能潜在发生的地方是继承层级中,假设我们在普通用户之上定义一个VIP用户。如下例所示:


1void logCall(const std::string &funcName);
 2
 3class Customer
 4{
 5public:
 6    // ...
 7    Customer(const Customer &rhs);            // 拷贝构造函数
 8    Customer &operator=(const Customer &rhs); // 拷贝赋值运算符函数
 9private:
10    std::string name;
11};
12
13Customer::Customer(const Customer &rhs) : name(rhs.name)
14{
15    logCall("Customer类的拷贝构造函数");
16}
17
18Customer &Customer::operator=(const Customer &rhs)
19{
20    logCall("Customer类的拷贝赋值运算符函数");
21    name = rhs.name;
22    return *this;
23}
24
25class PriorityCustomer: public Customer{
26public:
27    // ...
28    PriorityCustomer(const PriorityCustomer& rhs);
29    PriorityCustomer& operator=(const PriorityCustomer& rhs);
30    // ...
31private:
32    int priority;
33};
34
35PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs): priority(rhs.priority){
36    logCall("子类PriorityCustomer的拷贝构造函数");
37}
38
39PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
40    logCall("子类PriorityCustomer的拷贝赋值运算符函数");
41    priority = rhs.priority;
42    return *this;
43}


看起来,子类PriorityCustomer的拷贝函数像是复制了PriorityCustomer类中的每一个成员。但是,注意:子类PriorityCustomer的拷贝函数仅仅复制了PriorityCustomer声明的成员变量,但子类PriorityCustomer中还包含了它从Customer中所继承过来的基类的成员变量副本,这些成员变量是没有被复制的。子类PriorityCustomer的拷贝构造函数并没有指定实参传递基类Customer的拷贝构造函数(即子类PriorityCustomer拷贝构造函数的初始化列表中没有提及Customer)。因此,子类PriorityCustomer对象中的Customer成分会被不带实参的Customer默认的拷贝构造函数来初始化。默认拷贝构造函数将对name和lastTransaction成员变量进行默认的初始化操作。但是,上述情况在子类PriorityCustomer的拷贝赋值运算符函数中有些许不同。子类PriorityCustomer的拷贝赋值运算符函数不会企图去修改基类中的成员变量,所以那些成员变量会保持不变。


3.局部拷贝现象的解决方法


解决方法:任何时候,只要你为子类自定义了拷贝函数,你必须很小心地也复制其基类的成员。那些成员往往是private的,所以你的子类无法直接访问这些成员,你应该让子类的拷贝函数调用相应的基类拷贝函数。如下例所示:


1PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs): Customer(rhs), priority(rhs.priority){             // 让子类的拷贝构造函数调用基类的拷贝构造函数
 2    logCall("子类PriorityCustomer的拷贝构造函数");
 3}
 4
 5PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
 6    logCall("子类PriorityCustomer的拷贝赋值运算符函数");
 7    Customer::operator=(rhs);   // 让子类的拷贝赋值运算符函数调用基类的拷贝赋值运算符函数
 8    priority = rhs.priority;
 9    return *this;
10}


4.代码优化


可能大家也会发现,上面各例中的拷贝构造函数与拷贝赋值运算符函数有相似的功能和代码,那么我们能不能为了避免代码重复,让其中一个函数调用另一个呢?答案是不能!因为使用拷贝赋值运算符函数调用拷贝构造函数,或者使用拷贝构造函数调用拷贝赋值运算符函数,都是没有意义的。拷贝赋值运算符函数适用于已经构造好的对象,而拷贝构造函数适用于还没有构造好的对象,所以这种做法在语义上是错误的。


如果我们真的想要节省代码,比如某个类有特别多的数据成员,我们可以写另一个第三方函数来给每个成员赋值,拷贝构造函数和拷贝赋值运算符函数都可以调用它,这个第三方函数一般叫init()。


5.总结


(1)拷贝函数应该确保拷贝对象内的所有成员变量以及所有子类中的基类成分。
(2)不要试图以拷贝构造函数或拷贝赋值运算符函数去调用拷贝赋值运算符函数或拷贝构造函数,正确的做法是可以另写一个第三方函数init()让两个函数来调用它。

相关文章
|
1月前
|
编译器 C++
C++之类与对象(完结撒花篇)(上)
C++之类与对象(完结撒花篇)(上)
36 0
|
14天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
46 4
|
15天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
43 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
28 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
25 4
|
1月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
17 0
|
1月前
|
编译器 C++ 数据库管理
C++之类与对象(完结撒花篇)(下)
C++之类与对象(完结撒花篇)(下)
32 0
|
1月前
|
编译器 C++
C++之类与对象(3)(下)
C++之类与对象(3)(下)
35 0
|
1月前
|
编译器 C++
C++之类与对象(3)(上)
C++之类与对象(3)
18 0
|
1月前
|
编译器 C++
C++之类与对象(2)
C++之类与对象(2)
31 0