c++のstruct構造関数を深く理解する

5345 ワード

structで定義されたデータ構造タイプが、私が構造をコピーしたり、割り当て操作をしたりしたときに何が起こるか知りたいですか?もし私の構造にポインタ参照オブジェクトが存在する場合、正しく処理できますか?これらの疑問を持ってstructの構造関数を研究し,以下のいくつかの疑問を解いた.
1)コンパイラがstructの構造関数を自動的に合成するタイミング
2)ポインタ参照オブジェクトを携帯するstructの正確なコピーまたはコピー構造を保証する方法
まず最初の問題を見て、次のコードを考えてみましょう.ServerConfigには2つの単純なメンバーしかいません.逆アセンブリ可視コンパイラによってServerConfigのコンストラクション関数が合成され、そのメンバーのコンストラクション関数が呼び出されます.addrメンバーを削除すると、コンパイラはServer Configのコンストラクタを合成しません.これにより、structメンバーにコンストラクション関数が存在する場合、コンパイラは自動的にコンストラクション関数を生成することがわかります.
しかしclassのデフォルト構造関数は必須ではないことに注意してください.つまり.デフォルトのコンストラクション関数は、仮想テーブルポインタを初期化するなど、プログラムの正確な実行を保証するためにコンパイラに必要です.プログラムにデフォルトの初期値などを提供するわけではありません.classがデフォルトコンストラクション関数を含む親クラスから継承された場合、デフォルトコンストラクション関数を持つメンバーが存在する場合、virtual functionが存在する場合、またはvirtual継承が存在する場合.コンパイラがデフォルトのコンストラクション関数を合成するのをトリガーします.
#include 
#include 
#include 

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 :    lea    -0x20(%rbp),%rax	                        # config1    rax
   0x0000000000400661 :    mov    %rax,%rdi                                #   rdi  this  
   0x0000000000400664 :    callq  0x4006ce <:serverconfig>	#   config1
   0x0000000000400669 :    mov    $0x0,%ebx
   0x000000000040066e :    lea    -0x20(%rbp),%rax
   0x0000000000400672 :    mov    %rax,%rdi
   0x0000000000400675 :    callq  0x4006ec <:>
   ...
 
(gdb) disassemble ServerConfig::ServerConfig
Dump of assembler code for function ServerConfig::ServerConfig():
   ...
   0x00000000004006d6 :     mov    %rdi,-0x8(%rbp)                          #   this  
   0x00000000004006da :    mov    -0x8(%rbp),%rax                          # 
   0x00000000004006de :    add    $0x8,%rax                                # this + 0x08,    port    addr   
   0x00000000004006e2 :    mov    %rax,%rdi                                # 
   0x00000000004006e5 :    callq  0x400684 <:cstring>            #   CString    
   ...

しかし、上記のコードは危険です.サーバコンフィグに対してコピー構造または付与操作を参照した場合、double freeが発生します.それはなぜですか?次のコードを考えてみましょう.コンパイル後、逆アセンブリを行います.ServerConfigがコピーコンストラクタを合成するのではなく、ビット単位でコピーされていることがわかります.したがってconfig 2はconfig 1内の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 :    lea    -0x30(%rbp),%rax                         #   config1  
   0x0000000000400661 :    mov    %rax,%rdi
   0x0000000000400664 :    callq  0x4006ea <:serverconfig>  #   config1
   0x0000000000400669 :    mov    -0x30(%rbp),%rax                         
   0x000000000040066d :    mov    %rax,-0x20(%rbp)                         #   config1.port   config2.port
   0x0000000000400671 :    mov    -0x28(%rbp),%rax               
   0x0000000000400675 :    mov    %rax,-0x18(%rbp)                         #   config1.addr.m_str   config2.addr.m_str
   0x0000000000400679 :    mov    $0x0,%ebx
   0x000000000040067e :    lea    -0x20(%rbp),%rax                         
   0x0000000000400682 :    mov    %rax,%rdi 
   0x0000000000400685 :    callq  0x400708 <:> #   config2
   0x000000000040068a :    lea    -0x30(%rbp),%rax
   0x000000000040068e :    mov    %rax,%rdi
   0x0000000000400691 :    callq  0x400708 <:> #   config1

次に、ポインタリファレンスオブジェクトを携帯するstructの正確なコピーまたはコピー構造を保証する方法について、2番目の質問に答えます.ポインタリファレンスのようなメンバーが含まれている場合は、コピーコンストラクション関数と付与操作関数を正しく宣言する必要があります.この例でCStringが次のように正しく宣言されている場合、コンパイラはサーバコンフィグのコピー構造関数と割り当て操作関数を正しく合成します.
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 :    callq  0x4007a6 <:serverconfig const="">
   0x000000000040067c :    lea    -0x20(%rbp),%rdx
   0x0000000000400680 :    lea    -0x30(%rbp),%rax
   0x0000000000400684 :    mov    %rdx,%rsi
   0x0000000000400687 :    mov    %rax,%rdi
   0x000000000040068a :    callq  0x4007e0 <:operator const="">
   ...

コピーが許可されていないことを明確にする場合は、コピー構造と付与操作関数を禁止する必要があります.次のようにベースクラスのコピーを禁止すればよい.
class IUncopyable
{
	public:
		~IUncopyable(){};
	private:
		IUncopyable(IUncopyable &);
		IUncopyable & operator=(const IUncopyable&);
};