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); ) を使う