⌈C++⌋深度剖析构造、拷贝构造与赋值运算符重载——深浅拷贝、explicit、类型转换等

本文介绍了C++中的拷贝构造函数和赋值运算符重载的概念,包括何时调用拷贝构造、深拷贝与浅拷贝的区别、编译器如何绕过拷贝构造以及explicit修饰符的作用。同时,文章讨论了赋值运算符重载的格式、默认行为以及为何必须定义为成员函数。最后,展示了现代C++中拷贝构造和赋值运算符的实现策略,强调了内存管理和避免无限递归的重要性。

目录

一、认识拷贝构造函数

1、什么是拷贝构造

2、深拷贝与浅拷贝

3、编译器可以绕过拷贝构造函数(C++ Primer P442)

4、explicit修饰

二、认识赋值运算符重载

1、赋值运算符重载格式

2、默认赋值运算符重载

3、赋值运算符都必须定义为成员函数

三、现代版拷贝构造与赋值运算符重载写法分析


一、认识拷贝构造函数

1、什么是拷贝构造

①当用一个已存在的对象创建一个新对象时,②当函数参数类型为类类型对象时,③当函数返回值类型为类类型对象时,编译器会自动调用拷贝构造函数;

(如下代码为,一个模拟string类的构造函数与拷贝构造⬇️)

namespace test {
	class string {
	public:
		//构造函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size + 1];
			memcpy(_str, str, _size + 1);
		}
		//拷贝构造
		string(const string& s) {   //考虑为什么要加 const 与 引用
			_str = new char[s._capacity + 1];
			memcpy(_str, s._str, s._size + 1);
			_size = s._size;
			_capacity = s._capacity;
		}
        //析构函数
		~string() {
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
	private:
		size_t _capacity;
		size_t _size;
		char* _str;
	};
}
int main() {

	test::string s1("for_test");  //调用构造函数

	test::string s2(s1);   //调用拷贝构造
	return 0;
}

🕛 拷贝构造传入单个形参(const string& s),该形参是改类类型的对象的引用,函数传参时如果不传入引用,会发生对象的拷贝(拷贝函数参数压入函数栈帧中),拷贝对象时又会再次调用拷贝构造,不断重复此步骤,引发无穷递归,导致栈溢出。

🕑const:由于是传入对象的引用,所以加上const,保证改对象在函数中不被修改;

2、深拷贝与浅拷贝

拷贝构造函数是构造函数的一个重载,与构造函数一样,如果不显式定义,编译器会自动生成默认的拷贝构造函数,默认的拷贝构造将内置类型的成员变量按照内存的字节序进行拷贝,而自定义类型会调用他自己的拷贝构造

那当我们不显示定义拷贝构造函数会出什么问题呢?⬇️

1️⃣成员变量所指向的地址空间相同:

无显示定义拷贝构造时(浅拷贝):

显示定义拷贝构造时:

2️⃣调用多次析构函数,对同一块空间进行多次释放:

如下方动画所示,当类中没有显式定义拷贝构造时,两个对象(s1, s2)的成员_str指向同一块空间,函数结束时,分别调用s2与s1的析构函数,导致_str所指向的空间被释放两次;

3、编译器可以绕过拷贝构造函数(C++ Primer P442)

在拷贝初始化过程中,编译器可以跳过拷贝/移动构造函数,直接创建对象,即:

	/*编译器被允许将第一行代码改写为第三行代码*/
    test::string s3 = "abcd";    //隐式调用拷贝构造
	test::string s4("abcd");     //编译器略过拷贝构造

两者汇编代码完全相同 

4、explicit修饰

上述第三点中的第一行代码,实质是将“abcd”实例化一个对象,再调用拷贝构造;

对于单个参数或者出第一个参数无默认值其余均有默认值的构造函数,具有上述类似的类型转换的作用;

当用explicit修饰构造函数后,该类型转换会被禁止!

1️⃣我们以上述代码为例:

2️⃣同样的例子我们还可以从vector的源代码中找到:

在vs编译阶段报错: 


二、认识赋值运算符重载

/**************以下为模拟实现string类的赋值运算符重载************/    

string& operator=(const string& s) {
            if (this == &s) return *this;   //判断是否自己给自己赋值

            char* tmp = new char[s._capacity + 1];     //注意存放'\0'的空间
            memcpy(tmp, s._str, s._capacity + 1);
            delete[] _str;    //释放之前的空间
            _str = tmp;
            _size = s._size;
            _capacity = s._capacity;

            return *this;   //返回左值引用
        }

1、赋值运算符重载格式

参数类型:const T&, 传入引用提高传参效率

②返回值类型: T&, 返回一个指向左侧运算对象的引用(*this), 为了与内置类型的赋值运算符保持一致,即可连续赋值

③检查是否自己给自己赋值

2、默认赋值运算符重载

与处理拷贝构造函数一样,如果一个类未定义自己的拷贝赋值运算符,编译器会为他生成一个合成拷贝赋值运算符(默认),以值的方式逐字节拷贝,同时也要注意深浅拷贝问题;

3、赋值运算符都必须定义为成员函数

我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数,因为如果用户在类外自己实现一个全局的赋值运算符,就和编译器在类中生成的默认赋值运算符重载冲突了


三、现代版拷贝构造与赋值运算符重载写法分析

//传参为类的引用的拷贝构造
string(const string& s) :_str(nullptr) {
    string tmp(s._str);   //调用传参为字符串的拷贝构造
    std::swap(tmp._str, _str);
    std::swap(tmp._size, _size);
    std::swap(tmp._capacity, _capacity);
}

string& operator=(string tmp) {   //调用拷贝构造 string tmp(s);
    std::swap(tmp._str, _str);
    std::swap(tmp._size, _size);
    std::swap(tmp._capacity, _capacity);

    return *this;
    //函数结束tmp对象销毁
}

参考文献:《C++ Primer》

### C++ 赋值运算符重载实现深拷贝 当类成员包含指针或其他动态分配的资源时,简单的按位复制可能导致多个对象共享同一份数据副本,这被称为浅拷贝。为了避免这种情况并确保每个对象都有自己独立的数据副本,则需执行深拷贝。 对于含有指向堆内存成员变量的情况,在`operator=`函数内部不仅要复制原始对象中的数值型字段,还需要手动管理新分配的空间来存储被复制的对象内容,并释放旧有的资源以防泄漏[^3]。 下面是一个具体的例子展示如何正确处理涉及字符串类型的复杂结构: ```cpp #include <iostream> #include <cstring> class String { public: char* data; // 构造函数初始化data为NULL String(): data(nullptr) {} // 参数化构造函数用于创建新的字符数组 explicit String(const char* str){ if(str){ size_t length = strlen(str); data = new char[length + 1]; strcpy(data, str); } else{ data = nullptr; } } ~String(){ delete[] data; // 清理已分配的内存 } // 防止重复删除同一个地址上的空间以及避免悬空指针问题 String& operator=(const String& rhs){ if(this == &rhs) return *this; delete [] this->data; // 先清理当前持有的资源 if(rhs.data){ size_t length = strlen(rhs.data); this->data = new char[length + 1]; strcpy(this->data,rhs.data); }else{ this->data=nullptr; } return *this; } }; ``` 在这个案例里,每当调用赋值操作(`=`),都会触发上述定义的方法来进行安全可靠的深层复制过程,从而保证两个不同实例间不会相互干扰各自所拥有的实际资料[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dusong_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值