C++ コピーコンストラクタと変換演算子と明示的な(explicit)コンストラクタ
こんな細かいことが気になるコードを書く時点で相当ダメなカンジですが,つい気になったので試してしまった.
C++界隈では相当使い古されたネタに違いない.
codepad : http://codepad.org/WxvP2RoB
#include <iostream> using namespace std; class B; int getdatafromb(const B& b); class A { friend class B; int data; public: // explicit を付けると引数渡しと戻り値におけるコピーコンストラクタが働かずエラー A(const A& a) : data(a.data) { cout << "A::A(const A&)" << endl; } // explicit A(int i) : data(i) { cout << "A::A(int)" << endl; } // -pedantic では 以下の explicit を削るとエラー explicit A(const B& b) : data(getdatafromb(b)) { cout << "A::A(const B&)" << endl; } A& operator=(const A& a) { this->data = a.data; cout << "A::operator=(const A&)" << endl; return *this; } A& operator=(const int& i) { this->data = i; cout << "A::operator=(const int&)" << endl; return *this; } A& operator=(const B& b) { this->data = getdatafromb(b); cout << "A::operator=(const B&)" << endl; return *this; } ~A() { cout << "A::~A()" << endl; } }; class B { friend int getdatafromb(const B& b); int data; public: B() : data(0) { cout << "B::B()" << endl; } operator A() const { cout << "B::operator A()" << endl; return A(data); } }; inline int getdatafromb(const B& b) { return b.data; } int main(int argc, char** argv) { cout << "a" << endl; A a(1); // A::A(int) cout << "b" << endl; A b = a; // A::A(const A&) .. copy-initialization cout << "c" << endl; A c(a); // A::A(const A&) .. direct-initialization cout << "d" << endl; A d = A(20); // A::A(int) のみ direct-initialization, then copy-initialization (最適化でcopy-は消える) cout << "assignment a=b" << endl; a = b; // A::operator=(const A&) // cout << "e" << endl; // A e = 1; // ^ error; A::A(int) に explicitが付いているため. // また intからA&への暗黙の変換はない (intクラスを作ってoperator A()を追加することはできない) cout << "f" << endl; B f; // B::B() cout << "g" << endl; A g(f); // A::A(const B&) .. direct-initialization cout << "h" << endl; A h = f; // B::operator A(), then copy-initialization (最適化でcopy-は消える) }
手元(gcc 4.0.1)での実行結果
a A::A(int) b A::A(const A&) c A::A(const A&) d A::A(int) assignment a=b A::operator=(const A&) f B::B() g A::A(const B&) h B::operator A() A::A(int) A::~A() A::~A() A::~A() A::~A() A::~A() A::~A()
- 手元のgcc 4.0.1 では copy-initializationでも コピーコンストラクタの呼び出しが除去された.(g++ -pedantic -O0)
- codepad版では一部 コピーコンストラクタの呼び出しが残った.
- operator= は初期化とは関係ない
- Bを使ったAの構築では,copy-initializationでは暗黙の型変換でB::operator AでAに変換した後にA::A(A&)を呼ぶが,direct-initializationは直接A::A(B&)を呼ぶ (09/07/09追記)
とりあえず今決めたポリシー
- 変換演算子 (メンバのoperator B())は使わない (More Effective C++の項目5も参照)
- コピーコンストラクタは(もし使う場合は)explicitにしない(当たり前?)
- 1引数コンストラクタはexplicitにする
- copy-initializationの構文 (A a = b;) は使わない. direct-initialization (A a(b); ) を使う