今日のメニュー:特殊食材(その1):Copy Constructor & Operator=

今日のポイント

今日は、親クラスのメソッドが継承されない特殊食材、copy constructor と operator= について考える。operator= は継承はされず自動生成されるが、その際、親クラスの operator= へ連鎖する。copy constructor も同様。自分で明示的に実装した場合、連鎖が起こるか否かは実装による。

調理実習

● レシピー
まず以下のテストプログラムを用意する。

#include <iostream>
#include <iomanip>
using namespace std;

class Base {
  public:
    Base(int val = 0) : fVal(val) {}
    virtual void Print() { cerr << "fVal = " << fVal < endl; }

 // この private 宣言を有効にするとコンパイルが通らない (gcc2 でも jlclogin でも HP でも)
#if 0
  private:
#endif
    Base & operator=(const Base &s)
    {
      cerr << "Hey! Base is called!" << endl;
      fVal = s.fVal;
      return *this;
    }

  private:
    int  fVal;
};

class Derived : public Base {
  public:
    Derived(int val = 1, int a = 2) : Base(val), fAdd(a) {}

    void Print()
    {
      Base::Print();
      cerr << "fAdd = " << fAdd << endl;
    }

  private:
    int fAdd;
};


int main()
{
   Derived d1;
   cerr << "d1()::::::" << endl;
   d1.Print();

   cerr << "d2(0,1):::" << endl;
   Derived d2(0,1);
   d2.Print();

   cerr << "d2=d1:::::" << endl;
   d2 = d1;
   d2.Print();

  return 0;
}


試食

これを test.cxx としコンパイル実行すると

$ g++ test.cxx
$ ./a.out
d1()::::::
fVal = 1
fAdd = 2
d2(0,1):::
fVal = 0
fAdd = 1
d2=d1:::::
Hey! Base is called!
fVal = 1
fAdd = 2

となる。

 

評価

つまり実装されていない Derived class の operator= は自動生成され、Derived class にしかない fAdd data member も自動的に代入される。親クラスの data member の代入は親クラスの operator= が呼ばれることで実行される(一番はじめに思ってた通りの振る舞い)。
親クラスに operator= が実装されていなければ親クラスの自動生成 operator= が呼ばれるという形で連鎖するようだ。つまり、自動生成 operator= は、自分の data member の代入は、単なる代入で、親クラスの data member の代入は親クラスの operator= を使って代入するという、自然な実装になっている。
親クラスに operator= が実装されていて、その中でそのまた親クラスの operator= を呼んでいなければそこで連鎖が切れる。
実際、上の例で、#if 0 を #if 1 にして Base::opertor= を private にしてしまうと、gcc3、gcc2 どちらでも RedHat9.0 (jlclogin) でも MacOSX (jlccdcmac) でも operator= は private だからアクセス違反だと怒られコンパイルできない。また、Base の operator= が呼ばれるという点も gcc の version や、ホストによらず同じ。gcc だけの問題かもしれないので HP(jlcuxf) の aCC (ヒューパーの ANSI C++ コンパイラー)でテストしてみても全く同じ結果。

 

教訓

つまり、「 operator= は確かに継承はされない。しかし、その意味は、子クラスの operator= が、明示的に実装されていなければ、子クラス特有の data member の代入を自動的に行うよう自動生成されるということ。親クラスの operator= が使われないという意味ではない。実際、この自動生成の operator= は、親クラスの data member の代入を行うため親クラスの operator= に連鎖する。」が答え。
自分で operator= を実装した場合は、親クラスへ連鎖するかどうかは実装による。
同様に、自動生成 copy constructor は、自分の data member はそのままコピーし、親の data member は親の copy constructor を呼ぶことでコピーする。
つまり「copy constructor も継承はされないが、明示的に実装されていなければ自動生成され、親クラスの copy constructor に連鎖する」ということ。
親に operator= や、copy constructor が明示的に実装されていなければ、自動生成の operator= や copy constructor の連鎖が続き、結果全ての data member の単純代入や単純コピーが実行されることになる。
明示的な実装がある場合、その中で連鎖を続けるような実装になっていない限り一般に連鎖は途切れる。
というわけで結論は、MacOS X の gcc3.1 がおかしいは濡衣。むしろ gcc2 で通ってしまうことの方がおかしい。