是否曾好奇struct定义的数据结构类型,当我拷贝构造时,或者赋值操作时会发生什么?倘若我结构中存在指针引用对象时,又能否正确处理?带着这些疑问,我们来对struct的构造函数进行研究,以解答以下几个疑问:
1) 何时编译器会自动为struct合成构造函数
2) 如何能保证携带指针引用对象的struct正确拷贝或拷贝构造
让我们先来看第一个问题,考虑如下代码。ServerConfig只有两个简单的成员,通过反汇编可见编译器合成了ServerConfig的构造函数,并调用其成员的构造函数。若我们移除addr成员,编译器则不会为ServerConfig合成构造函数。由此不难发现,当struct成员存在构造函数时,编译器会自动为其生成构造函数。
但是值得注意class的默认构造函数不是必须的,也就是说。默认构造函数是编译器所需要的,它用以保证程序的正确运行,如初始化虚表指针;并非为程序提供默认初始值之类。当class继承自含默认构造函数的父类时,具有默认构造函数的成员时,存在virtual function时,或者virtual继承时; 会触发编译器合成默认构造函数。
-
-
-
-
-
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地址放入rax
-
0x0000000000400661 <+20>: mov %rax,%rdi # 通过rdi传入this指针
-
0x0000000000400664 <+23>: callq 0x4006ce <ServerConfig::ServerConfig()> # 构造config1
-
0x0000000000400669 <+28>: mov $0x0,%ebx
-
0x000000000040066e <+33>: lea -0x20(%rbp),%rax
-
0x0000000000400672 <+37>: mov %rax,%rdi
-
0x0000000000400675 <+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,%rdi
-
0x0000000000400664 <+23>: callq 0x4006ea <ServerConfig::ServerConfig()> # 构造config1
-
0x0000000000400669 <+28>: mov -0x30(%rbp),%rax
-
0x000000000040066d <+32>: mov %rax,-0x20(%rbp) # 拷贝config1.port 到 config2.port
-
0x0000000000400671 <+36>: mov -0x28(%rbp),%rax
-
0x0000000000400675 <+40>: mov %rax,-0x18(%rbp) # 拷贝config1.addr.m_str 到 config2.addr.m_str
-
0x0000000000400679 <+44>: mov $0x0,%ebx
-
0x000000000040067e <+49>: lea -0x20(%rbp),%rax
-
0x0000000000400682 <+53>: mov %rax,%rdi
-
0x0000000000400685 <+56>: callq 0x400708 <ServerConfig::~ServerConfig()> # 析构config2
-
0x000000000040068a <+61>: lea -0x30(%rbp),%rax
-
0x000000000040068e <+65>: mov %rax,%rdi
-
0x0000000000400691 <+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),%rdx
-
0x0000000000400680 <+51>: lea -0x30(%rbp),%rax
-
0x0000000000400684 <+55>: mov %rdx,%rsi
-
0x0000000000400687 <+58>: mov %rax,%rdi
-
0x000000000040068a <+61>: callq 0x4007e0 <ServerConfig::operator=(ServerConfig const&)>
-
...
当我们明确不允许拷贝的时候,一定要禁止拷贝构造和赋值操作函数。可以继承如下禁止拷贝基类即可。
-
class IUncopyable
-
{
-
public:
-
~IUncopyable(){};
-
private:
-
IUncopyable(IUncopyable &);
-
IUncopyable & operator=(const IUncopyable&);
-
};
---------------------
作者:EINPROGRESS
来源:CSDN
原文:https://blog.csdn.net/kwanson/article/details/82320728
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件