深度解析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 ...
随机推荐
- [权限相关]在PeopleSoft中查找可以使用DataMover的用户
DataMover是一个功能非常强大的工具,它的访问权限应该被控制,特别是在PRD环境中.在每个公司,许多用户可能在一段时间内切换部门,角色和职责,所以每隔一段时间就应该检查这些用户的权限,以确认他们 ...
- FreeRTOS源代码的编程标准与命名约定
编程标准 (Coding Standard) FreeRTOS 源代码遵守 MISRA (Motor Industry Software Reliability Association) 规范. 与 ...
- java webservice简单的例子
开发环境:eclipse .jdk 创建服务端 demo-webservice 创建类 HelloService.java package com.hundsun.ws.service; import ...
- The request sent by the client was syntactically incorrect问题解决
The request sent by the client was syntactically incorrect意思嘛,google翻译一下 通过日志不难看出,是由参数不匹配造成的. 所以对于Da ...
- 一份关于npm的新手指南
Node.js使得在服务器端使用JavaScript编写应用程序成为可能.它是基于V8Javascript运行时并且使用C++编写的,所以它的速度很快.最初,它旨在作为应用程序的服务器环境,但是开发人 ...
- Java 枚举7常见种用法(转)
JDK1.5引入了新的类型——枚举.在 Java 中它虽然算个“小”功能,却给我的开发带来了“大”方便. 用法一:常量 在JDK1.5 之前,我们定义常量都是: public static fianl ...
- 开启属于你的GNOME桌面
图片剪辑源自美剧<黑客军团>(英语:Mr. Robot) GNOME(GNU Network ObjectEnvironment)是一种GNU网络对象模型环境 ,GNU计划的一部分,目的为 ...
- cloudstack下libvirtd服务无响应问题
在cloudstack4.5.2版本下,偶尔出现libvirtd服务无响应的情况,导致virsh命令无法使用,同时伴随cloudstack master丢失该slave主机连接的情况.最初怀疑是lib ...
- html超级简单实现点赞(收藏)和取消赞效果
1.前言 我们经常会遇到对一些列表呀进行点赞呀收藏数据等效果呀.今天就用html+css实现超级简单易上手的点赞和取消赞的demo展示. 2.详情 1.css样式 .like{ font-size:6 ...
- Scrapy提取多个标签的text
对于要提取嵌套标签所有内容的情况, 使用string或//text(), 注意两者区别 >>> from scrapy import Selector >>> &g ...