深度解析C++拷贝构造函数

自2003年开始,断断续续用了12年C++,直到这两年做物联网嵌入式开发,感觉对C++的掌握仅有10%左右。 习惯了C#开发,C++倒显得难以下手!今天就一个函数返回问题跟辉月兄弟讨论一番,大有所获,足以解决我们目前80%的问题,感觉对C++的掌握上升到了20%。 背景,现有字节数组ByteArray和字符串String,(不要激动,单片机嵌入式C++很难用起来标准类库) 我们需要实现函数String& ByteArray::ToHex() 其实这是我们在C#上非常常用的函数,把一个字节数组转为字符串,然后别的地方使用或者显示出来。C#原型String ToHex(this Byte[] buf) 这里有一个老大难题: 1,如果ToHex内部栈分配字符串空间,把字节数组填充进去,那么离开ToHex的时候栈回收,对象数据无效 2,如果ToHex内部堆分配空间,字节数组填充,离开ToHex的时候得到指针。但是这样违背了C/C++谁申请谁释放的原则,其它小伙伴使用ToHex的时候可能忘了释放 3,最后只能折中,做成String& ByteArray::ToHex(String& str); 别提多憋屈!最受不了的是,外部分配str的时候,还得考虑数组有多长!这些本来最好由ToHex内部解决的问题。 总之,这个问题就这样折腾了我12年! 知道今天,跟辉月兄弟聊起这个问题,他也有十多年C++历史,用得比我要多一些。他有一段常用代码大概如下:

CString Test()
{
        CString a = "aaaa";
        CString b = "bbbb";
        CString c = a + b;

        return c;
}

按他说法,就这样子写了十多年! 我说c不是栈分配吗?离开的时候会被析构吧,外部怎么可能拿到?他说是哦,从来没有考虑过这个问题。 我们敏锐的察觉到,C++一定可以实现类似的做法,因为字符串相加就是最常见的例子。 经过一番探讨,我们发现关键点出在拷贝构造函数上面 测试环境:编译器Keil MDK 5.14,处理器STM32F407VG 1、进出两次拷贝 做了一个测试代码,两次调用拷贝构造函数

class A
{
public:
        char* str;

    A(char* s)
    {
                str = s;
        debug_printf("A %s 0x%08X\r\n", str, this);
    }
        A(const A &a)
        {
        debug_printf("A.Copy %s 0x%08X => %s 0x%08X\r\n", a.str, &a, str, this);
        }
    ~A()
    {
        debug_printf("~A %s 0x%08X\r\n", str, this);
    }
};

class B : public A
{
public:
    B(char* s) : A(s)
    {
        debug_printf("B %s 0x%08X\r\n", str, this);
    }
        B(const B &b) : A(b.str)
        {
        debug_printf("B.Copy %s 0x%08X => %s 0x%08X\r\n", b.str, &b, str, this);
        }
    ~B()
    {
        debug_printf("~B %s 0x%08X\r\n", str, this);
    }
        B& operator=(const B &b)
        {
        debug_printf("B.Assign %s 0x%08X => %s 0x%08X\r\n", b.str, &b, str, this);
                return *this;
        }
};

B fun(B c)
{
        c.str = "c";
    return c;
}

void CtorTest()
{
        B a("a"), b("b");
        debug_printf("start \r\n");
    b = fun(a);
        debug_printf("end \r\n");
}

执行结果如下:

A a 0x2001FB78
B a 0x2001FB78
A b 0x2001FB74
B b 0x2001FB74
start 
A a 0x2001FB7C
B.Copy a 0x2001FB78 => a 0x2001FB7C
A c 0x2001FB80
B.Copy c 0x2001FB7C => c 0x2001FB80
B.Assign c 0x2001FB80 => b 0x2001FB74
~B c 0x2001FB80
~A c 0x2001FB80
~B c 0x2001FB7C
~A c 0x2001FB7C
end 
~B b 0x2001FB74
~A b 0x2001FB74
~B a 0x2001FB78
~A a 0x2001FB78
  • 进入func的时候,参数进行了一次拷贝,c构造,也就是7C,然后a拷贝给c
  • 离开func的时候,产生了临时对象80,并把7C拷贝给80
  • func返回值赋值给b,也就是临时对象80赋值给74
  • 然后才是80和7C的析构。
  • 那么关键点就在于这个临时对象,它的作用域横跨函数内部和调用者,自然不怕析构回收。
  • 不过奇怪的是,内部参数7C为何在外面析构??

2、进去拷贝出来引用 修改func函数,返回引用,少一次拷贝构造

B& fun(B c)
{
        c.str = "c";
    return c;
}

执行结果如下:

A a 0x2001FB70
B a 0x2001FB70
A b 0x2001FB6C
B b 0x2001FB6C
start 
A a 0x2001FB74
B.Copy a 0x2001FB70 => a 0x2001FB74
B.Assign c 0x2001FB74 => b 0x2001FB6C
~B c 0x2001FB74
~A c 0x2001FB74
end 
~B b 0x2001FB6C
~A b 0x2001FB6C

~A a 0x2001FB70
  • 进去的时候参数来了一次拷贝构造74
  • 出来的时候74直接赋值给6C,也就是b。看样子,按引用返回直接省去了临时对象。
  • 但是上面这个代码编译会有一个警告,也就是返回本地变量的引用。
  • 赋值以后,内部对象74才被析构
  • 虽然有警告,但是对象还没有被析构,外面可以使用。按理说每个线程都有自己的栈,不至于那么快被别的线程篡改数据。但是很难说硬件中断函数会不会用到那一块内存。
  • 这里有个非常奇怪的现象,没有见到70的B析构,不知道是不是串口输出信息太快,丢失了这一部分数据,尝试了几次都是如此。

3、引用进去引用出来 修改参数传入引用,再少一次拷贝构造

B& fun(B& c)
{
        c.str = "c";
    return c;
}

执行结果如下:

A a 0x2001FB88
B a 0x2001FB88
A b 0x2001FB84
B b 0x2001FB84
start 
B.Assign c 0x2001FB88 => b 0x2001FB84
end 
~B b 0x2001FB84
~A b 0x2001FB84
~B c 0x2001FB88
~A c 0x2001FB88
  • 更加彻底,没有任何拷贝构造函数被执行
  • 并且没有“返回本地变量引用”的警告

End

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏web前端教室

网易云课堂公开课-Promise【文字版】

今天的网易云课堂的公开课已经结束,因为是第一次,所以有些操作也不太熟练,下面是本次公开课的文字内容。

15820
来自专栏chafezhou

fire让命令行接口更简单

14250
来自专栏deepcc

javascript 中的 delete

36580
来自专栏web前端教室

js不好学并不是因为它难,而是因为它容易混淆

这段时间我主讲的前端零基础课,让我感觉js这东西是越来越简单了。你当然可以说我这是越讲课越熟。确实是有这部分因素,但更主要的是,我发现js中虽然有许多的概念各不...

27570
来自专栏企鹅号快讯

verilog编程要素整理时刻牢记

verilog编程建议 1、不使用初始化语句; 2、不使用延时语句; 3、不使用循环次数不确定的语句,如:forever,while等; 4、尽量采用同步方式设...

20380
来自专栏做全栈攻城狮

电脑小白自学软件编程-.Net语法基础之循环语句,纯技巧干货

课程总目录:因头条无法自定义目录,大家关注:“做全栈攻城狮”微信公众号。回复“.net目录”,即可获取。微信公众号也包含大量学习教程,等你来~

15030
来自专栏喵了个咪的博客空间

zephir-(12)php函数和异常处理

#zephir-php函数和异常处理# ? ##前言## 先在这里感谢各位zephir开源技术提供者 经过了一个多月的学习,zephir的文档译文和基础讲解也将...

36760
来自专栏点滴积累

Python扩展方法一二事

前言 跟着一个有强迫症的老板干活是一件极其幸福的事情(你懂的)。最近碰到一个问题,简单的说就是对一个对象做出部分修改后仍然返回此对象,于是我就写了一个方法,老板...

37360
来自专栏黑泽君的专栏

java基础学习_IO流03_字符流、IO流小结、案例_day21总结

10120
来自专栏黑泽君的专栏

传智播客_风清扬_2015年java基础深入浅出版_刘意老师

视频百度网盘下载链接:https://pan.baidu.com/s/1pLc7AvL#list/path=%2F

1K10

扫码关注云+社区

领取腾讯云代金券