默认构造函数
一. 基础
1.1 默认构造函数
类的默认构造函数分为5种.其中拷贝赋值和移动赋值仅改变对象的值,不创建新的对象,所以返回值是引用.
- 默认构造
- 拷贝构造
- 深拷贝
- 浅拷贝
- 移动构造
- 拷贝赋值
- 移动赋值
1.2 左值引用和右值引用
参考网址:
- https://zhuanlan.zhihu.com/p/374392832
有句很通俗的话来区分左值和右值, 即能取地址的是左值,否则为右值.
左值引用和右值引用本身都是引用,多用于函数传参时,避免内存拷贝.
不同的是左值引用针对的是变量,而右值引用针对的是临时变量(Foo()
) 或者是一些常量 ("string"
,1
,2
,3
等).不过相同的是,都是取它们的引用,不会发生内存拷贝.
1.3 std::move
参考网址:
std::move 只是取了变量的右值引用,并没有在内存方面有什么动作,它只做了这一点点事.
std::move 后,将成员转成右值,会调用指定的接收右值参数的函数,如移动构造,至于 std::move 后原变量是否可以使用,主要看上述调用的函数,但是一般来说,都不建议使用,因为使用 move, 往往代表着所有权转移了,旧对象不再拥有它.
二. 测试
下面是我对上述五种默认构造函数的测试.
#include <iostream>
using namespace std;
class Foo1
{
public:
Foo1() { cout << "Foo1 默认构造函数" << endl; }
Foo1(const Foo1 &foo) { cout << "Foo1 拷贝构造函数" << endl; }
Foo1(const Foo1 &&foo) { cout << "Foo1 移动构造函数" << endl; }
Foo1 &operator=(const Foo1 &foo) { cout << "Foo1 拷贝赋值函数" << endl; }
Foo1 &operator=(const Foo1 &&foo) { cout << "Foo1 移动赋值函数" << endl; }
};
class Foo2
{
public:
Foo2() { cout << "Foo2 默认构造函数" << endl; }
Foo2(const Foo2 &foo) { cout << "Foo2 拷贝构造函数" << endl; }
Foo2(const Foo2 &&foo) { cout << "Foo2 移动构造函数" << endl; }
Foo2 &operator=(const Foo2 &foo) { cout << "Foo2 拷贝赋值函数" << endl; }
Foo2 &operator=(const Foo2 &&foo) { cout << "Foo2 移动赋值函数" << endl; }
};
class Foo3
{
public:
// Foo3() = default;
private:
Foo1 foo1;
Foo2 foo2;
};
int main()
{
Foo3 f1;
std::cout << std::endl;
Foo3 f2(f1);
std::cout << std::endl;
Foo3 f3(std::move(f1));
std::cout << std::endl;
Foo3 f4;
f4 = f1;
f4 = std::move(f1);
std::cout << std::endl;
Foo3 f5 = f1;
Foo3 f6 = std::move(f1);
std::cout << std::endl;
}
2.1 拷贝构造
可以看到当调用 Foo3 f2(f1);
时, 输出的是
Foo1 拷贝构造函数
Foo2 拷贝构造函数
说明默认调用了子成员的拷贝构造函数
2.2 移动构造
可以看到当调用 Foo3 f3(std::move(f1));
时, 输出的是
Foo1 移动构造函数
Foo2 移动构造函数
说明默认调用了子成员的移动构造函数
2.3 拷贝赋值和移动赋值
当调用
Foo3 f4;
f4 = f1;
f4 = std::move(f1);
输出的是
Foo1 默认构造函数
Foo2 默认构造函数
Foo1 拷贝赋值函数
Foo2 拷贝赋值函数
Foo1 移动赋值函数
Foo2 移动赋值函数
默认调用了子成员的拷贝赋值和移动赋值
2.4 编译器优化
2.4.1 默认重载 =
当我们使用在对象创建的时候使用 =
,编译器将不会调用默认重载函数,会自动优化调用相应的拷贝构造或移动构造函数
Foo3 f5 = f1;
Foo3 f6 = std::move(f1);
输出
Foo1 拷贝构造函数
Foo2 拷贝构造函数
Foo1 移动构造函数
Foo2 移动构造函数
说明没有调用默认=
重载函数,而是分别调用了默认拷贝构造和默认移动构造函数.
2.4.2 手动重载 =
将 Foo3的 = 手动重载
class Foo3
{
public:
Foo3() = default;
Foo3(const Foo3 &f) = default;
Foo3(Foo3 &&f) = default;
Foo3 &operator=(const Foo3 &f) { cout << "Foo3 拷贝赋值函数" << endl; }
Foo3 &operator=(const Foo3 &&f) { cout << "Foo3 移动赋值函数" << endl; }
private:
Foo1 foo1;
Foo2 foo2;
};
调用
Foo3 f5 = f1;
Foo3 f6 = std::move(f1);
此时输出
Foo1 拷贝构造函数
Foo2 拷贝构造函数
Foo1 移动构造函数
Foo2 移动构造函数
说明定义的重载函数此刻没有生效,编译器仍然对其进行了优化
需要注意的是:
因为我们手动重载了=
,此时默认重载函数就被覆盖了,进行拷贝赋值或移动赋值时,不会再调用其子成员的赋值函数了.
三. 重点
- 默认移动构造函数会自动调用其子成员的函数. 若子成员没有移动构造,则调用拷贝,如基础类型(INT, float); 若子成员有移动构造,则调用子成员的移动构造函数,如复合类型(std::string, std::vector).
- 若没有接收右值参数的函数时,就会转而调用左值.