深度解析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 ...
随机推荐
- STK卫星工具箱下载
简介 STK的全称是Satellite Tool Kit(卫星工具箱),STK/Pro 9.0最新出品,完整版,是由Analytical Graphics公司开发的一款在航天工业领域中处于绝对领先地位 ...
- 用subline text写PHP后台服务器POST请求
1 运行XAMPP程序,看到Apache Web Server 是Running状态即可 2 打开Subline text ,新建一个PHP文件,选择保存路径:应用程序->XAMPP->h ...
- java对Microsoft Document的操作--->对Excel的操作
起初,自己想对网站上爬取一些数据来写到Excel表格中,便在网上找了找java操作Excel接口,了解到Apache的POI接口可以对微软的文档进行操作,WIKI搜索的结果如下, HSSF - 提供读 ...
- 修改wampsever默认密码
wamp初始默认mysql账号:root 密码: 空 准备修改为,账号:root 密码:xys829475 1.在phpMyAdmin界面中点击[用户],将用户概况中的所有用户名为[root]的用户的 ...
- MySQL日期、时间相关内容
欢迎和大家交流技术相关问题: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://g ...
- 推荐60个jQuery插件(转)
jQuery插件jQuery Spin Button自定义文本框数值自增或自减 jQuery插件JQuery Pager分页器实现javascript分页功能 jQuery插件FontSizer实现J ...
- java大数 斐波那契数列
java大数做斐波那契数列: 思路:1. 2.可以用数组存着 import java.math.BigInteger; import java.util.Scanner; public ...
- Hbase架构与原理
Hbase架构与原理 HBase是一个分布式的.面向列的开源数据库,该技术来源于 Fay Chang所撰写的Google论文"Bigtable:一个结构化数据的分布式存储系统".就 ...
- python实战===使用随机的163账号发送邮件
import linecache import smtplib import time import linecache import random #算出txt的行数,163账号_2.txt中,每一 ...
- 【有意思的BUG】未名
这个帖子描述定位一个BUG的思路. 开始了. 用浏览器访问某一个网址http://111.aaa.com/ ,如果发现提示异常,那么接下来该如何定位BUG呢? 用相同的浏览器去访问不同域(不是aaa. ...