是否曾好奇struct定义的数据结构类型,当我拷贝构造时,或者赋值操作时会发生什么?倘若我结构中存在指针引用对象时,又能否正确处理?带着这些疑问,我们来对struct的构造函数进行研究,以解答以下几个疑问:

1) 何时编译器会自动为struct合成构造函数

2) 如何能保证携带指针引用对象的struct正确拷贝或拷贝构造

让我们先来看第一个问题,考虑如下代码。ServerConfig只有两个简单的成员,通过反汇编可见编译器合成了ServerConfig的构造函数,并调用其成员的构造函数。若我们移除addr成员,编译器则不会为ServerConfig合成构造函数。由此不难发现,当struct成员存在构造函数时,编译器会自动为其生成构造函数。

但是值得注意class的默认构造函数不是必须的,也就是说。默认构造函数是编译器所需要的,它用以保证程序的正确运行,如初始化虚表指针;并非为程序提供默认初始值之类。当class继承自含默认构造函数的父类时,具有默认构造函数的成员时,存在virtual function时,或者virtual继承时; 会触发编译器合成默认构造函数。

#include <stdio.h>
#include <string.h>
#include <stdint.h>class CString
{public:CString(){ m_str = strdup("");}CString(const char *str){ m_str = strdup(str);}~CString(){ delete m_str; m_str= NULL;}private:char *m_str;
};
typedef struct {int port;CString addr;
}ServerConfig;
int main(int argc, char *argv[])
{ServerConfig config1;return 0;
}
(gdb) disassemble main
Dump of assembler code for function main(int, char**):...0x000000000040065d <+16>:    lea    -0x20(%rbp),%rax                            # config1地址放入rax0x0000000000400661 <+20>:    mov    %rax,%rdi                                # 通过rdi传入this指针0x0000000000400664 <+23>:    callq  0x4006ce <ServerConfig::ServerConfig()> # 构造config10x0000000000400669 <+28>:    mov    $0x0,%ebx0x000000000040066e <+33>:    lea    -0x20(%rbp),%rax0x0000000000400672 <+37>:    mov    %rax,%rdi0x0000000000400675 <+40>:    callq  0x4006ec <ServerConfig::~ServerConfig()>...(gdb) disassemble ServerConfig::ServerConfig
Dump of assembler code for function ServerConfig::ServerConfig():...0x00000000004006d6 <+8>:     mov    %rdi,-0x8(%rbp)                          # 获取this指针0x00000000004006da <+12>:    mov    -0x8(%rbp),%rax                          # 0x00000000004006de <+16>:    add    $0x8,%rax                                # this + 0x08, 偏移掉port计算得到addr的地址0x00000000004006e2 <+20>:    mov    %rax,%rdi                                # 0x00000000004006e5 <+23>:    callq  0x400684 <CString::CString()>            # 调用CString构造函数...

但是上面的代码是危险的,只要我们对ServerConfig引用了拷贝构造或者赋值操作时,会引发double free。那这又是为什么呢?让我们考虑如下代码,编译后我们进行反汇编。不难发现ServerConfig并未合成拷贝构造函数,而是进行了按位拷贝。因此config2拷贝了config1内addr成员携带的指针值而非指针引用对象,引起重复释放。

int main(int argc, char *argv[])
{ServerConfig config1;ServerConfig config2 = config1;return 0;
}
(gdb) disassemble main
Dump of assembler code for function main(int, char**):...0x000000000040065d <+16>:    lea    -0x30(%rbp),%rax                         # 获取config1地址0x0000000000400661 <+20>:    mov    %rax,%rdi0x0000000000400664 <+23>:    callq  0x4006ea <ServerConfig::ServerConfig()>  # 构造config10x0000000000400669 <+28>:    mov    -0x30(%rbp),%rax                         0x000000000040066d <+32>:    mov    %rax,-0x20(%rbp)                         # 拷贝config1.port 到 config2.port0x0000000000400671 <+36>:    mov    -0x28(%rbp),%rax               0x0000000000400675 <+40>:    mov    %rax,-0x18(%rbp)                         # 拷贝config1.addr.m_str 到 config2.addr.m_str0x0000000000400679 <+44>:    mov    $0x0,%ebx0x000000000040067e <+49>:    lea    -0x20(%rbp),%rax                         0x0000000000400682 <+53>:    mov    %rax,%rdi 0x0000000000400685 <+56>:    callq  0x400708 <ServerConfig::~ServerConfig()> # 析构config20x000000000040068a <+61>:    lea    -0x30(%rbp),%rax0x000000000040068e <+65>:    mov    %rax,%rdi0x0000000000400691 <+68>:    callq  0x400708 <ServerConfig::~ServerConfig()> # 析构config1

我们现在开始回答第二个问题,如何能保证携带指针引用对象的struct正确拷贝或拷贝构造。那就是其含有指针引用之类的成员,都应正确声明拷贝构造函数和赋值操作函数。如本例中CString正确声明如下,这样编译器会正确为ServerConfig合成拷贝构造函数和赋值操作函数。

class CString
{public:CString(){ m_str = strdup("");}CString(const char *str){ m_str = strdup(str);}CString(const CString &cstr){  m_str = strdup(cstr.m_str);}CString &operator =(const CString &cstr){delete m_str; m_str = strdup(cstr.m_str);return *this;}~CString(){ delete m_str; m_str= NULL;}private:char *m_str;
};
(gdb) disassemble main
Dump of assembler code for function main(int, char**):...0x0000000000400677 <+42>:    callq  0x4007a6 <ServerConfig::ServerConfig(ServerConfig const&)>0x000000000040067c <+47>:    lea    -0x20(%rbp),%rdx0x0000000000400680 <+51>:    lea    -0x30(%rbp),%rax0x0000000000400684 <+55>:    mov    %rdx,%rsi0x0000000000400687 <+58>:    mov    %rax,%rdi0x000000000040068a <+61>:    callq  0x4007e0 <ServerConfig::operator=(ServerConfig const&)>...

当我们明确不允许拷贝的时候,一定要禁止拷贝构造和赋值操作函数。可以继承如下禁止拷贝基类即可。

class IUncopyable
{public:~IUncopyable(){};private:IUncopyable(IUncopyable &);IUncopyable & operator=(const IUncopyable&);
};

深入理解c++之struct构造函数相关推荐

  1. C++之struct构造函数(2010-10-19 15:04:47)

    C++之struct构造函数 (2010-10-19 15:04:47) 转载 标签: cpp struct 构造函数 校园 分类: C/C_PlusPlus 在网络协议.通信控制.嵌入式系统的C/C ...

  2. C++之struct构造函数

    C++之struct构造函数 在网络协议.通信控制.嵌入式系统的C/C++编程中,我们经常要传送的不是简单的字节流(char型数组),而是多种数据组合起来的一个整体,其表现形式是一个结构体. 下面看看 ...

  3. JavaScript --- [学习笔记]观察者模式 理解对象 工厂模式 构造函数模式

    说明 本系列(JS基础梳理)为后面TCP的模拟实现做准备 本篇的主要内容: 观察者模式.工厂模式.构造函数模式 和 对对象的理解 1. 观察者模式 参考JavaScript设计模式 1.1 消息注册方 ...

  4. 深入理解C++类的构造函数与析构函数

    在研究 C++ 类的继承.派生.组合时,一直没有清晰地了解构造函数与析构函数的调用过程.本章通过点 - 线组合类,来深入分析组合类情况下,对象的构造与析构. 1.问题的引入 源代码: <span ...

  5. 理解C++中拷贝构造函数

    拷贝构造函数的功能是用一个已有的对象来初始化一个被创建的同样对象,是一种特殊的构造函数,具有一般构造函数的所有特性,当创建一个新对象的时候系统会自动调用它:其形参是本类对象的引用,它的特殊功能是将参数 ...

  6. java 线程的构造函数_[c++11]多线程编程(二)——理解线程类的构造函数

    构造函数的参数 std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数. 第一参数的类型并不是c语言中的 ...

  7. 深入理解Kotlin无参构造函数

    Unsafe 创建实例 在java中 创建一个对象 其实主要就是3种方法 通过new 关键字来创建 这种是最常见的 通过反射构造方法来创建对象 这种也不少见.很多框架中都有使用. Unsafe类来创建 ...

  8. 【深入理解C++】拷贝构造函数

    文章目录 1.拷贝构造函数 2.默认的拷贝操作 3.默认拷贝构造函数 4.何时调用拷贝构造函数 1.拷贝构造函数 拷贝构造函数是构造函数的一种.当利用已存在的对象创建一个新对象时,就会调用新对象的拷贝 ...

  9. javaScript 对象添加属性和创建js对象的方式(以及理解:“无法给构造函数添加新的属性“)

    1.javaScript 对象想要添加属性,非常简单 (1)直接添加,使用语法:objectName.propertyName 添加属性. 举例: var person = new Object(); ...

  10. struct的构造函数

    C++之struct构造函数 (2010-10-19 15:04:47) 转载 标签: cpp struct 构造函数 校园 分类: C/C_PlusPlus 在网络协议.通信控制.嵌入式系统的C/C ...

最新文章

  1. LinkQueue的基本创建
  2. Yii框架官方指南系列43——专题:URL(创建、路由、美化及自定义)
  3. 桌面虚拟化对企业IT的四大贡献
  4. 4.0.13 mysql 注入_Windows2000下整合Mysql4.0.13与Tomcat4.1.24搭建Jsp环境
  5. android nfc peer to peer 实例,NFC Peer2Peer Mode - Android Beam - ISO 18092
  6. mediawiki java_使用MediaWiki 1.16.0实现添加媒体向导
  7. 论文公式编号右对齐_论文不会写?最详细的论文排版技巧
  8. 2017.10.3 国庆清北 D3T2 公交车
  9. Python: 字符串
  10. Python基础_字符串的格式化
  11. 锚点链接页面元素定位(JQuery)
  12. 使用python进行数学建模系列1 读表格 +简单处理+ 画图简单入门 代码可直接运行
  13. 哈夫曼树的构造和哈夫曼编码实现详细讲解(含例题详细讲解)
  14. java保护表格_java poi Excel单元格保护
  15. GNSS定位中的不同高度概念及计算
  16. 百度云网盘一直显示“下载请求中”,一个 解决办法
  17. 数学函数最小值为什么可以通过导数=0来求出呢?
  18. C#-钉钉开发H5应用-事件订阅
  19. 0x0报错解决--win11预览版升级报错0x0的快速解决方案
  20. 用c语言验证5阶魔方矩阵,穷举法打印n阶魔方矩阵

热门文章

  1. tp6配置使用Redis
  2. 各类邮箱POP3和SMTP服务器地址和端口
  3. MyBatis事务管理
  4. 求边长为一的正方体中,面对角线组成的正四面体体积.
  5. 计算机专业对未来职业的理想追求,IT行业个人职业生涯规划
  6. Kylin多维分析引擎(四):Kylin Cude构建流程详解
  7. navicat本地同步到navicat cloud
  8. excel两个指标相关性分析_相关分析与回归分析 Excel 和 R计算皮尔逊相关系数(Pearson correlation)...
  9. 2021上半年教资综合素质——主观题
  10. 软件发明专利实例_软件系统专利申请案例