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()让两个函数来调用它。

目录
打赏
0
0
0
0
6
分享
相关文章
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
1月前
|
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
70 19
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
53 13
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
157 5
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
173 4
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
258 4
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
48 4