文章目录
- 前言
- 1. 拷贝构造函数 (Copy Constructor)
- 2. 赋值构造函数 (Copy Assignment Operator)
- 3. 移动构造函数 (Move Constructor)
- 4. 移动赋值构造函数 (Move Assignment Operator)
- 总结
前言
C++中关于一个对象的构造,就有很多讲究。
其中最常用的可能就是拷贝构造函数和赋值构造函数。
后面的移动相关的就用的很少,每次面试的时候会刷一下面经,突击一下概念,工作中用到少了,久而久之就忘了。
这篇文章就对与一个对象的几种构造方式做一个总结。
1. 拷贝构造函数 (Copy Constructor)
定义:拷贝构造函数用于创建一个新对象
,该新对象是现有对象的副本
。它的函数签名通常是:
ClassName(const ClassName& other);
作用:当你使用一个对象初始化另一个对象时,编译器会调用拷贝构造函数。例如:
ClassName obj1;
ClassName obj2 = obj1; // 调用拷贝构造函数
实现:如果类中包含动态分配的资源(如指针),需要自定义拷贝构造函数以确保深拷贝:
class MyClass {
public:
int* data;
MyClass(int value) : data(new int(value)) {}
// 拷贝构造函数
MyClass(const MyClass& other) : data(new int(*other.data)) {}
~MyClass() { delete data; }
};
2. 赋值构造函数 (Copy Assignment Operator)
定义:赋值构造函数用于将一个已存在的对象
的内容赋值给另一个已存在的对象
。其函数签名通常是:
ClassName& operator=(const ClassName& other);
作用:当你用一个对象的值来赋值给另一个已存在对象时,编译器会调用赋值构造函数。例如:
ClassName obj1;
ClassName obj2;
obj2 = obj1; // 调用赋值构造函数
实现:和拷贝构造函数类似,赋值构造函数也需要处理动态资源,以避免资源泄漏或浅拷贝的问题:
class MyClass {
public:
int* data;
MyClass(int value) : data(new int(value)) {}
// 拷贝构造函数
MyClass(const MyClass& other) : data(new int(*other.data)) {}
// 赋值构造函数
MyClass& operator=(const MyClass& other) {
if (this == other) return *this; // 自赋值检查
delete data;
data = new int(*other.data);
return *this;
}
~MyClass() { delete data; }
};
拷贝构造函数和赋值构造函数对比:
概念理解上:
拷贝构造是创建一个新对象,新对象是通过一个已存在的对象进行初始化,新对象的内容是已存在对象的副本。
赋值构造函数是对一个已经创建好的对象,用另一个已经存在的对象进行内容赋值。
使用形式上:
拷贝构造
ClassName obj1;
ClassName obj2 = obj1; // 调用拷贝构造函数
当然一般我们这样用:ClassName obj2(&obj1);
赋值构造函数
ClassName obj1;
ClassName obj2;
obj2 = obj1; // 调用赋值构造函数
这里和上面构造不同的地方在于,obj2先创建了,再进行赋值。虽然都是可以用=
的方式进行初始化。
而且,赋值构造函数,其实是重载了运算符=
,所以这个就是我们自定义的一种赋值运算,这个赋值运算
刚好又给一个对象进行初始化,所以叫做赋值构造函数。
讲移动构造函数、移动赋值构造函数的概念之前,我们先要明白一个概念,
什么是左值和右值
就我目前的理解来说,我认为
左值
:在内存中分配了内存进行存放的变量/对象/…等;它是有实际的存放地址的;
它一般位于运算符左侧,比如int a=1,a就是左值,因为它是一个内存中的变量,有实际的内存地址,同时在运算符左侧。
右值
:属于一种临时变量,在脱离作用域后自动释放。没有实际存放的地址;
它一般位于运算符左侧,比如int a=1,1就是右值,因为它不是一个内存中的变量,没实际的内存地址,同时在运算符右侧。
还有一些我们的中间变量,比如int a=b+c;假如这里b和c都是声明的int类型变量,但是b+c得到的值其实是一个右值,因为它只是一个中间的临时变量。
左值通常表示对象的命名变量,可以用于取地址(有持久存储)。
右值通常表示临时对象或常量,不能用于取地址(无持久存储)。右值引用允许将资源从一个对象“移动”到另一个对象,而不是进行深拷贝。
3. 移动构造函数 (Move Constructor)
定义:移动构造函数用于将资源从一个临时对象(右值)转移到新创建的对象中。其函数签名通常是:
ClassName(ClassName&& other) noexcept;
作用:当一个对象被移动而不是拷贝时(例如,返回一个对象或使用 std::move),编译器调用移动构造函数。例如:
ClassName obj1;
ClassName obj2 = std::move(obj1); // 调用移动构造函数
实现:移动构造函数通常会将源对象的资源直接转移给新对象,然后将源对象的资源指针置为 nullptr:
class MyClass {
public:
int* data;
MyClass(int value) : data(new int(value)) {}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
}
~MyClass() { delete data; }
};
4. 移动赋值构造函数 (Move Assignment Operator)
定义:移动赋值构造函数用于将一个临时对象(右值)的资源转移到另一个已存在的对象中。其函数签名通常是:
ClassName& operator=(ClassName&& other) noexcept;
作用:当你将一个临时对象的资源赋值给一个已存在的对象时,编译器会调用移动赋值构造函数。例如:
ClassName obj1;
ClassName obj2;
obj2 = std::move(obj1); // 调用移动赋值构造函数
实现:移动赋值构造函数通常需要释放当前对象的资源,并转移源对象的资源,然后将源对象的资源指针置为 nullptr:
class MyClass {
public:
int* data;
MyClass(int value) : data(new int(value)) {}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
}
// 移动赋值构造函数
MyClass& operator=(MyClass&& other) noexcept {
if (this == &other) return *this; // 自赋值检查
delete data; // 释放当前资源
data = other.data; // 转移资源
other.data = nullptr;
return *this;
}
~MyClass() { delete data; }
};
移动构造与非移动构造对比
概念理解上:
这个我们对比一下拷贝构造函数和赋值构造。
移动的概念就是,给数据的对象是一个临时变量,而非移动构造是一个具体的对象。
移动构造就是把一个临时对象的值通过std::move 移动到新对象
移动赋值构造就是把一个临时对象的值通过std::move 移动到已存在对象
所以其实拷贝构造和赋值构造最大的区别就在于被构造的对象是不是已存在。
使用形式上:
移动的这种构造,都需要std::move这个函数。
同时需要用到两个引用符&&
,这里两个&是因为传的是引用参数。
关于&的个数问题,这里做一个解释
左值引用用一个&表示(T&),可以绑定到左值。
右值引用用两个&表示(T&&),可以绑定到右值。
总结
1.拷贝构造函数 用于创建一个新对象作为现有对象的副本。
2.赋值构造函数 用于将一个对象的值赋给另一个已经存在的对象。
3.移动构造函数 用于将资源从一个临时对象转移到新对象中,避免不必要的资源复制。
4.移动赋值构造函数 用于将临时对象的资源转移到一个已经存在的对象中,并释放现有对象的资源。