c++ 默认构造函数讲解(拷贝构造,移动构造,拷贝赋值,移动赋值)

默认构造函数

一. 基础

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).
  • 若没有接收右值参数的函数时,就会转而调用左值.
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值