深度解析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
深度解析C++拷贝构造函数的更多相关文章
- C++ 默认拷贝构造函数 深度拷贝和浅拷贝
C++类默认拷贝构造函数的弊端 C++类的中有两个特殊的构造函数,(1)无参构造函数,(2)拷贝构造函数.它们的特殊之处在于: (1) 当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数且 ...
- 【转】C++的拷贝构造函数深度解读,值得一看
建议看原帖 地址:http://blog.csdn.net/lwbeyond/article/details/6202256 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很 ...
- Kafka深度解析
本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/01/02/Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅 ...
- java内存分配和String类型的深度解析
[尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...
- c++ 构造函数,拷贝构造函数,析构函数与赋值操作符
题目: 为下面的Rectangle类实现构造函数,拷贝构造函数,赋值操作符,析构函数. class Shape { int no; }; class Point { int x; int y; }; ...
- 【C++对象模型】构造函数语意学之二 拷贝构造函数
关于默认拷贝构造函数,有一点和默认构造函数类似,就是编译器只有在[需要的时候]才去合成默认的拷贝构造函数. 在什么时候才是[需要的时候]呢? 也就是类不展现[bitwise copy semantic ...
- 编写类String的构造函数、拷贝构造函数、析构函数和赋值函数
一.题目: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &othe ...
- 拷贝构造函数,深拷贝,大约delete和default相关业务,explicit,给定初始类,构造函数和析构函数,成员函数和内联函数,关于记忆储存,默认参数,静态功能和正常功能,const功能,朋友
1.拷贝构造 //拷贝构造的规则,有两种方式实现初始化. //1.一个是通过在后面:a(x),b(y)的方式实现初始化. //2.另外一种初始化的方式是直接在构造方法里面实现初始化. 案比例如以 ...
- String深度解析
文章出处:http://my.oschina.net/xiaohui249/blog/170013 一.引题 String类型是比较特殊的一种类型,同时也是面试经常被问到的一个知识点,本文结合java ...
随机推荐
- 小白也能看懂的插件化DroidPlugin原理(二)-- 反射机制和Hook入门
前言:在上一篇博文<小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理>中详细介绍了 DroidPlugin 原理中涉及到的动态代理模式,看完上篇博文后你就会发现原来动态代 ...
- [编辑器]vim常用操作
我是ide的用户,对于vim一只停留在:打开.看.写.关闭基本操作,因为现在更多的接触linux服务器,所以为了提高 效率,用好vim是必备技能!下面罗列一些vim的常用操作,用做备忘(不断更新): ...
- Hadoop之HDFS及NameNode单点故障解决方案
Hadoop之HDFS 版权声明:本文为yunshuxueyuan原创文章.如需转载请标明出处: http://www.cnblogs.com/sxt-zkys/QQ技术交流群:299142667 H ...
- (转)ZXing生成二维码和带logo的二维码,模仿微信生成二维码效果
场景:移动支付需要对二维码的生成与部署有所了解,掌握目前主流的二维码生成技术. 1 ZXing 生成二维码 首先说下,QRCode是日本人开发的,ZXing是google开发,barcode4j也是老 ...
- [js] 实现接口
摘要 js 语言并没有interface implement 关键字,如果要做到和后端语言类似的接口,一般会需要模拟实现. 在oo 编程中, 接口可以让架构师规定好模块必须要实现的方法, 尽量解耦模块 ...
- Hive笔记——技术点汇总
目录 · 概况 · 手工安装 · 引言 · 创建HDFS目录 · 创建元数据库 · 配置文件 · 测试 · 原理 · 架构 · 与关系型数据库对比 · API · WordCount · 命令 · 数 ...
- 小程序server-3-搭建WebSocket 服务
小程序server-3-搭建WebSocket 服务: 1.安装 Node 模块 使用 ws 模块来在服务器上支持 WebSocket 协议,下面使用 NPM 来安装: cd /var/www/wxp ...
- Go语言学习笔记(六)net
加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 net import "net" net包提供了可移植的网络I/O接口,包括TCP/IP.UD ...
- NodeJS之queryString
前面的话 无论是前端还是后端,经常出现的应用场景是URL中参数的处理.nodeJS的queryString模块提供了一些处理 query strings 的工具.本文将详细介绍nodeJS中的quer ...
- 31. leetcode 122. Best Time to Buy and Sell Stock II
122. Best Time to Buy and Sell Stock II Say you have an array for which the ith element is the price ...