簡単な例を見てみましょう:
struct some_struct {
std::string str;
int a, b, c;
}
some_struct abc, abc_copy;
abc.str = "some text";
abc.a = 1;
abc.b = 2;
abc.c = 3;
abc_copy = abc;
それからabc_copyは正確にcopyのabc
...どうすればなし定義the = operator?
(これは、いくつかのコードで作業しているときに驚きました。)
これらの4つのメソッド(C++ 11では6つ)を定義しない場合、コンパイラーはそれらを生成します。
理由を知りたいなら?
Cとの下位互換性を維持するためです(C構造体は=および宣言で使用できるため)。ただし、単純なクラスの記述も容易になります。 「浅いコピーの問題」が原因で問題が増えると主張する人もいます。それに対する私の主張は、所有するRAWポインターを含むクラスを持たないことです。適切なスマートポインターを使用することで、問題はなくなります。
デフォルトコンストラクター(他のコンストラクターが定義されていない場合)
コンパイラが生成したデフォルトコンストラクターは、ベースクラスのデフォルトコンストラクターを呼び出し、次に各メンバーのデフォルトコンストラクターを(宣言された順序で)呼び出します
デストラクタ(デストラクタが定義されていない場合)
宣言の逆の順序で各メンバーのデストラクタを呼び出します。次に、基本クラスのデストラクタを呼び出します。
コピーコンストラクター(コピーコンストラクターが定義されていない場合)
Srcオブジェクトを渡す基本クラスコピーコンストラクターを呼び出します。次に、srcオブジェクトのメンバーをコピーする値として使用して、各メンバーのコピーコンストラクターを呼び出します。
割り当て演算子
Srcオブジェクトを渡して、基本クラス割り当て演算子を呼び出します。次に、コピーする値としてsrcオブジェクトを使用して、各メンバーで割り当て演算子を呼び出します。
移動コンストラクター(移動コンストラクターが定義されていない場合)
Srcオブジェクトを渡す基本クラス移動コンストラクターを呼び出します。次に、srcオブジェクトのメンバーを移動する値として使用して、各メンバーの移動コンストラクターを呼び出します。
移動演算子
Srcオブジェクトを渡す基本クラスの移動代入演算子を呼び出します。次に、コピーする値としてsrcオブジェクトを使用して、各メンバーで移動割り当て演算子を呼び出します。
このようなクラスを定義する場合:
struct some_struct: public some_base
{
std::string str1;
int a;
float b;
char* c;
std::string str2;
};
コンパイラが構築するものは次のとおりです。
struct some_struct: public some_base
{
std::string str1;
int a;
float b;
char* c;
std::string str2;
// Conceptually two different versions of the default constructor are built
// One is for value-initialization the other for zero-initialization
// The one used depends on how the object is declared.
// some_struct* a = new some_struct; // value-initialized
// some_struct* b = new some_struct(); // zero-initialized
// some_struct c; // value-initialized
// some_struct d = some_struct(); // zero-initialized
// Note: Just because there are conceptually two constructors does not mean
// there are actually two built.
// value-initialize version
some_struct()
: some_base() // value-initialize base (if compiler generated)
, str1() // has a normal constructor so just call it
// PODS not initialized
, str2()
{}
// zero-initialize version
some_struct()
: some_base() // zero-initialize base (if compiler generated)
, str1() // has a normal constructor so just call it.
, a(0)
, b(0)
, c(0) // 0 is NULL
, str2()
// Initialize all padding to zero
{}
some_struct(some_struct const& copy)
: some_base(copy)
, str1(copy.str1)
, a(copy.a)
, b(copy.b)
, c(copy.c)
, str2(copy.str2)
{}
some_struct& operator=(some_struct const& copy)
{
some_base::operator=(copy);
str1 = copy.str1;
a = copy.a;
b = copy.b;
c = copy.c;
str2 = copy.str2;
return *this;
}
~some_struct()
{}
// Note the below is pseudo code
// Also note member destruction happens after user code.
// In the compiler generated version the user code is empty
: ~str2()
// PODs don't have destructor
, ~str1()
, ~some_base();
// End of destructor here.
// In C++11 we also have Move constructor and move assignment.
some_struct(some_struct&& copy)
// ^^^^ Notice the double &&
: some_base(std::move(copy))
, str1(std::move(copy.str1))
, a(std::move(copy.a))
, b(std::move(copy.b))
, c(std::move(copy.c))
, str2(std::move(copy.str2))
{}
some_struct& operator=(some_struct&& copy)
// ^^^^ Notice the double &&
{
some_base::operator=(std::move(copy));
str1 = std::move(copy.str1);
a = std::move(copy.a);
b = std::move(copy.b);
c = std::move(copy.c);
str2 = std::move(copy.str2);
return *this;
}
};
C++では、構造体は、メンバーがデフォルトでプライベートアクセスではなくパブリックアクセスするクラスと同等です。
C++コンパイラは、クラスの次の特別なメンバーが提供されていない場合、自動的に生成します。
この動作は、Cとのソース互換性を維持するために必要です。
Cには、演算子を定義/オーバーライドする機能はありません。そのため、構造体は通常=演算子でコピーされます。
しかし、それは定義されています。標準では。演算子=を指定しない場合、演算子=が提供されます。そして、デフォルトの演算子は各メンバー変数をコピーするだけです。そして、各メンバーをコピーする方法をどのように知っていますか?演算子=(定義されていない場合、デフォルトで提供されます...)を呼び出します。
代入演算子(operator=
)は、C++の構造体またはクラスに対して暗黙的に生成される関数の1つです。
以下は、暗黙的に生成された4つのメンバーを説明するリファレンスです。
http://www.cs.ucf.edu/~leavens/larchc++manual/lcpp_136.html
要するに、暗黙的に生成されたメンバーは memberwise shallow copy を実行します。リンクされたページの長いバージョンは次のとおりです。
暗黙的に生成される代入演算子の仕様は、必要に応じて次のとおりです。仕様では、結果は割り当てられるオブジェクト(
self
)であり、ポストステートself
"のself
の抽象値の値は同じであると述べています。引数from
の抽象値の値として。
// @(#)$Id: default_assignment_op.lh,v 1.3 1998/08/27 22:42:13 leavens Exp $
#include "default_interfaces.lh"
T& T::operator = (const T& from) throw();
//@ behavior {
//@ requires assigned(from, any) /\ assigned(from\any, any);
//@ modifies self;
//@ ensures result = self /\ self" = from\any\any;
//@ ensures redundantly assigned(self, post) /\ assigned(self', post);
// thus
//@ ensures redundantly assigned(result, post) /\ assigned(result', post);
//@ }
自分で明示的に定義しない場合、コンパイラはいくつかのメンバーを合成します。割り当て演算子はそれらの1つです。コピーコンストラクターは別のものであり、デストラクタも取得します。独自のコンストラクタを提供しない場合も、デフォルトのコンストラクタを取得します。それ以外には何がわからないが、他にもあるかもしれないと思う(280Z28で与えられた答えのリンクはそうではないことを示唆している。
構造体は、基本的にメモリ内のコンポーネントの連結です(位置合わせのためにいくつかの可能なパディングが組み込まれています)。ある構造体に別の構造体の値を割り当てると、値はそのまま処理されます。