operator=()をoverrideできるのか?
operator=()をoverrideできるのか?
できるかできないかであれば、できる。
しかし、しない方が良いだろう。
ベースクラスのポインターを保持する関数などで派生クラスを含めたコピーをしたい場合どうなるか。
確認をした。
なお今回のお題では呼び出し側でのキャストはなしにする(複数の派生クラスを使っている場合を想定)。
// ベースクラス class TBase { protected: int n; public: TBase( int s ) : n(s) {} TBase( const TBase& ) = default; TBase( TBase&& ) = default; TBase& operator=( const TBase& ) = default; TBase& operator=( TBase&& ) = default; virtual ~TBase() = default; int get_n() { return n; } virtual int get_m() = 0; }; //--------------------------------------------------------------------------- // 派生クラス class TChild1 : public TBase { protected: int m; public: TChild1( int s, int p ) : TBase(s), m(p) {} TChild1( const TChild1& ) = default; TChild1( TChild1&& ) = default; TChild1& operator=( const TChild1& ) = default; TChild1& operator=( TChild1&& ) = default; virtual ~TChild1() = default; virtual int get_m() override { return m; } };
上のような環境で、ベースクラスのポインターでコピーをするとどうなるか。
TBase* s1 = new TChild1(2, 12); TBase* s2 = new TChild1(3, 13) ; *s2 = *s1; // s2->n=2, s2->m=13
operator=()の呼び出しは、TBaseのoperator=()が用いられる。TChild1のoperator=()は引数が違うので用いられない。
TBaseとTChildのoperator=()にvirtualをつけても、引数が違うので別の仮想関数になり、やはりTChild1のoperator=()は呼び出しされない。
もちろん下のように両方のポインターをキャストすればできるが、その場合はvirtualを付けなくても変わらない。
但し、キャストできたか確認が必要になる。
TBase* s1 = new TChild1(2, 12); TBase* s2 = new TChild1(3, 13) ; *dynamic_cast<TChild1*>(s2) = *dynamic_cast<TChild1*>(s1); // s2->n=2, s2->m=12
では、TBaseのoperator=()をvitrualにして、派生関数側でも定義すればどうか?
class TBase { protected: int n; public: TBase( int s ) : n(s) {} TBase( const TBase& ) = default; TBase( TBase&& ) = default; virtual TBase& operator=( const TBase& ) = default; virtual TBase& operator=( TBase&& ) = default; virtual ~TBase() = default; int get_n() { return n; } virtual int get_m() = 0; }; //--------------------------------------------------------------------------- class TChild2 : public TBase { protected: int m; public: TChild2( int s, int p ) : TBase(s), m(p) {} TChild2( const TChild2& ) = default; TChild2( TChild2&& ) = default; TChild2& operator=( const TChild2& ) = default; TChild2& operator=( TChild2&& ) = default; virtual ~TChild2() = default; virtual TBase& operator=( const TBase& lhs ) override { const TChild2& Lhs = static_cast<const TChild2& >(lhs); return operator=( Lhs ); }; virtual int get_m() override { return m; } };
おなじく、ベースクラスのポインターでコピーをするとどうなるか。
TBase* s1 = new TChild2(2, 12); TBase* s2 = new TChild2(3, 13) ; *s2 = *s1; // s2->n=2, s2->m=12
operator=()の呼び出しは、TChild2のoperator=( const TBase& )が用いられる。
この書き方で、意図した結果を得られることが判った。
ただし、この書き方をすると保守管理が大変なことが予想できる。=の挙動が場合によって変わる可能性もあるからだ。
例えば、s1が別の派生クラスであった場合、結果は悲惨なことになる。
やはり別関数を導入した方が良いのだろう。
class TBase { protected: int n; public: TBase( int s ) : n(s) {} TBase( const TBase& ) = default; TBase( TBase&& ) = default; TBase& operator=( const TBase& ) = default; TBase& operator=( TBase&& ) = default; virtual ~TBase() = default; int get_n() { return n; } virtual int get_m() = 0; virtual void assign( const TBase* pBase ) { operator=( *pBase ); } }; //--------------------------------------------------------------------------- class TChild3 : public TBase { protected: int m; public: TChild3( int s, int p ) : TBase(s), m(p) {} TChild3( const TChild3& ) = default; TChild3( TChild3&& ) = default; TChild3& operator=( const TChild3& ) = default; TChild3& operator=( TChild3&& ) = default; virtual ~TChild3() = default; virtual int get_m() override { return m; } virtual void assign( const TBase* pBase ) override { TChild3* pChild = dynamic_cast<const TChild3*>(pBase); if( pChild3 == nullptr ) throw false; // 例外を発生させる operator=( *pChild3 ); } };
TBaseのassignは使わないのであれば純粋仮想関数にしても良いだろう。
TBase* s1 = new TChild3(2, 12); TBase* s2 = new TChild3(3, 13) ; s2->assign( s1 ); // s2->n=2, s2->m=12
関数名を適切に指定することで、何をしたいのかもよく判る。