“复制赋值”和“移动赋值”的思考

概述

从 C++ 11 中开始,该语言支持两种类型的分配:复制赋值移动赋值。其中的内部细节是咋样的呢?今天跟踪了一下,是个蛮有趣的过程。下面我们以一个简单的类来做个分析。

#ifndef HASPTR_H
#define HASPTR_H

#include <string>

class HasPtr {
public:
    friend void swap(HasPtr&, HasPtr&);
    HasPtr(const std::string& s = std::string());
    HasPtr(const HasPtr& hp);
    HasPtr(HasPtr&& p) noexcept;
    HasPtr& operator=(HasPtr rhs);
    // HasPtr& operator=(const HasPtr &rhs);
    // HasPtr& operator=(HasPtr &&rhs) noexcept;
    ~HasPtr();

private:
    std::string* ps;
    int i;
};

#endif // HASPTR_H

#include "hasptr.h"
#include <iostream>

inline void swap(HasPtr& lhs, HasPtr& rhs)
{
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    std::cout << "call swap" << std::endl;
}

HasPtr::HasPtr(const std::string& s) : ps(new std::string(s)), i()
{
    std::cout << "call constructor" << std::endl;
}

//这里的i+1只是为了方便调试的时候看过程,实际是不用加1的
HasPtr::HasPtr(const HasPtr& hp) : ps(new std::string(*hp.ps)), i(hp.i + 1)
{
    std::cout << "call copy constructor" << std::endl;
}

HasPtr::HasPtr(HasPtr&& p) noexcept : ps(p.ps), i(p.i)
{
    p.ps = 0;
    std::cout << "call move constructor" << std::endl;
}

HasPtr& HasPtr::operator=(HasPtr rhs)
{
    swap(*this, rhs);
    return *this;
}

HasPtr::~HasPtr()
{
    std::cout << "call destructor" << std::endl;
    delete ps;
}

主函数

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    HasPtr hp1("hello"), hp2("World"), *pH = new HasPtr("World");
    hp1 = hp2;
    hp1 = std::move(*pH);

    return a.exec();
}

下面我们开始调试:

输出:

我们通过构造函数构造了三个变量,他们的值和

  address ps i
hp1 0x28fe64 "hello" 0
hp2 0x28fe5c "World" 0
pH 0x28fe9c "World" 0

复制赋值

我们接着单步走:

可以发现首先调用了复制构造函数,构造了一个和hp2一样的临时变量

  address ps i
this 0x28fe2c "World" 1
hp2 0x28fe5c "World" 0

下一步:

到这里才开始进行赋值运算,我们对比一下数据:

  address ps i
this 0x28fe2c "hello" 0
rhs 0x28fe8c "World" 1

这里的rhs就是我们刚刚分配的临时变量,那么this就是hp1,所以最终是我们的临时变量和hp1交换,我们接着走:

这里lhs的地址就是:0x28fe64,就是hp1的地址,交换之后:

到此hp1和临时变量的值就完全交换过来了,也就是说hp1 = hp1了。

  address ps i
lhs 0x28fe64 "World" 1
rhs 0x28fe8c "hello" 0

可是我们接着运行,发现进入了一个析构函数:

看一下地址是0x28fe8c以及其值,这是临时变量,临时变量不用了,所以被销毁了,至此我们的复制赋值运算就结束了。

移动赋值

我们看一下移动赋值赋值:

首先进入移动函数,这里只是使指针指向了pH的数据,并未构造新的数据,变量右值引用了pH,只是相当于换了个名字。

接下来开始进入赋值运算:

这里两个交换的值是hp1和pH,和复制赋值不同,它是和临时变量交换数据,

后面进入析构函数:

它释放掉了pH的数据。

可以看出来,pH的值被释放掉了。

总结

调试过后,我们发现,赋值运算的过程并非像想象中那么简单,是不是?复制赋值还是开辟一个临时变量用于转化,这个耗费了额外的空间资源。

移动赋值就可以避免这个问题,但是需要注意的是,移动赋值使用的是右值,用完之后就被销毁了,所以,如果想把一个左值当做右值来用,必须确保这个左值在这之后不需要使用了。

参考:

  1.   《C++ primer》
时间: 2024-10-12 19:30:09

“复制赋值”和“移动赋值”的思考的相关文章

【c++】深赋值与浅赋值

// 深赋值与浅赋值 // 浅赋值,这样的浅赋值会导致程序崩溃,与浅拷贝一个理 #include <iostream> using namespace std; class S_Evaluate; ostream& operator<<(ostream& out, const S_Evaluate &s); class S_Evaluate { friend ostream& operator<<(ostream& out, co

PHP变量引用赋值与变量赋值变量的区别

变量默认总是传值赋值.那也就是说,当将一个表达式的值赋予一个变量时,整个原始表达式的值被赋值到目标变量.这意味着,例如,当一个变量的值赋予另外一个变量时,改变其中一个变量的值,将不会影响到另外一个变量.有关这种类型的赋值操作,请参阅表达式一章. <?php $foo='abc'; $b=$foo; $b="my name is $b"; echo "$b"; //my name is abc echo "$foo"; // abc PHP

变量的直接赋值和间接赋值

直接赋值:直接赋予参数值的方式称为直接赋值. 间接赋值:由交互的方式赋值为间接赋值.(a就是一个变量) 原文地址:https://www.cnblogs.com/zteng/p/10303728.html

拷贝(复制)构造函数和赋值函数

String::String(const String &other)  //拷贝构造函数 { cout << "自定义拷贝构造函数" << endl; int length = strlen(other.m_data); m_data = new char[length + 1]; strcpy(m_data, other.m_data); } String & String::operator=(const String &other

Js的引用赋值与传值赋值

要说js的赋值方式时首先要说明js的数值类型:基本类型和引用类型. 1.基本类型 基本的数据类型有:undefined,boolean,number,string,null. 基本类型存放在栈区,访问是按值访问的,就是说你可以操作保存在变量中的实际的值. 当基本类型的数据赋值时,赋得是实际的值,a和b是没有关联关系的,b由a复制得到,相互独立.(字面量的才是基本类型) var a=10; var b=a; console.log(a+','+b);    // 10,10a++;console.

赋值构造函数(重载赋值操作符)(c++常问问题二)

*什么是赋值构造函数(重载赋值操作符) 下面的代码演示了什么是赋值构造函数,如果不人为定义赋值构造函数,系统将默认给你分配一个浅拷贝的赋值构造函数(下面例子为深拷贝的赋值操作) class cat { public: //构造函数 cat():m_pMyName(NULL),m_unAge(0) { cout<<"cat defult ctor"<<endl; } //子类赋值构造函数(重载赋值操作符) cat& operator=(cat& o

传值赋值与引用赋值详解

1. 基础概念 传值赋值:当将一个表达式的值赋予一个变量时,整个原始表达式的值被赋予到目标变量.这意味着,例如,当一个变量的值赋予另一个变量时,改变其中一个变量的值,将不会影响到另一个变量. 引用赋值:这意味着新的变量简单的引用(换言之,“成为其别名” 或者 “指向”)了原始变量.改动新的变量将影响到原始变量,反之亦然. 1 <?php 2 $a = 'a'; 3 $b = 'b'; 4 echo "$a,$b<hr />"; 5 // 显示a,b 6 $b = $a

【java解惑】复合赋值与简单赋值

复合赋值操作符有:+=.-=.*=./=.%=. <<=. >>=. >>>=.&=. ^=和| = : 简单赋值操作符为= : 如下所示代码: public class Example009 { public static void main(String[] args) { short x = 1; int x1 = 1; int i = 123456; x += i; //赋值1 x1 += i;//赋值2 System.out.println(&q

01PHP 引用赋值和传递赋值

PHP中变量的引用赋值是通过&符号进行的,在这里我们介绍下&符号引入的作用 1.先介绍下传递赋值 <?php $a=1; $b=3;                         从内存的角度上分析:$a指向一个地址,对应的数据是1   $b指向一个地址,对应的数据是3 $a=$b;                         现在执行$a=$b 是将$b中的数值赋给$a 这时 $a=3 echo $a,$b://3  3              因此两个变量的值都是3 3