(Translated by https://www.hiragana.jp/)
Fenrir's BLog: operator=()と継承

March 14, 2008

operator=()と継承けいしょう

拙作せっさくのC++行列ぎょうれつライブラリmatrix.hのメンテナンスをひさしぶりにやってみているのですが、部分ぶぶん行列ぎょうれつ代入だいにゅう解釈かいしゃくすこかんがえることがありました。簡易かんいてきなコードでくと以下いかのような問題もんだいです。

Matrix m1, m2;
m.partialMatrix = m2;

このとき部分ぶぶん行列ぎょうれつへの代入だいにゅうは、もと行列ぎょうれつえもおこなうべき(うえれいでいうとm1も変更へんこうされてしかるべき)であるとおもいます。現在げんざい実装じっそうでは行列ぎょうれつ構成こうせいする要素ようそデータ、そして行列ぎょうれつというはこ、これら2つを別々べつべつ管理かんりするフライウェイトパターンもちいていました。そのため代入だいにゅうについては要素ようそごとの代入だいにゅうおこなわず中身なかみ全体ぜんたいをつけかえる動作どうさをしており、上記じょうきのようなもと行列ぎょうれつまでえるということを想定そうていしていませんでした。

そこで部分ぶぶん行列ぎょうれつ適当てきとうクラスで表現ひょうげんし、クラスで代入だいにゅう演算えんざんのオーバーロードをおこなうことによって、上記じょうき問題もんだい解決かいけつをはかろうとしました。つまり部分ぶぶん行列ぎょうれつへの代入だいにゅう要素ようそごとの代入だいにゅうおこな一方いっぽう、それ以外いがい行列ぎょうれつでは要素ようそをまとめてえるだけというフライウェイトパターンの共存きょうぞん目指めざしました。

前置まえおきがながくなってしまいましたが、ここで表題ひょうだいにあるとおりの代入だいにゅう演算えんざん問題もんだいっかかりました。どうも代入だいにゅう演算えんざん継承けいしょうさきでオーバーロードしたさい、そのオーバーライド(言葉ことばがややこしいですね、わらい)が通常つうじょう関数かんすうことなるようなのです。ちがいをあきらかにするため、検証けんしょうようのプログラムoperator_equal_test.cppいてみました。要約ようやくをすると問題もんだいしょうじるのは以下いかのようなケースでした。

class A {
    public:
        A(){}
        virtual ~A() {}

        self_t &operator=(const A &a){
            cout << "A::operator=(A)";
            return *this;
        }
};

class B : public A {
    public:
        B() : A() {}
        ~B() {}
        self_t &operator=(const A &a){
            cout << "B::operator=(A)";
            return *this;
        }
};

int main(){
    A a;
    B b;

    cout << "a = a => "; a = a; cout << endl; // (1)
    cout << "a = b => "; a = b; cout << endl; // (2)

    cout << "b = a => "; b = a; cout << endl; // (3)
    cout << "b = b => "; b = b; cout << endl; // (4)

    return 0;
}

さて結果けっかはどうなるでしょうか。(1)、(2)ではAが左辺さへんオペランド、(3)、(4)ではBが左辺さへんオペランドなので通常つうじょう関数かんすう同様どうようかんがえるのであれば、(1)、(2)ではA::operator=(A)が、(3)、(4)ではB::operator=(A)がされそうなものです。しかし(4)では意表いひょうをついてA::operator=(A)がばれます。VC2005/2008 Express Edition、gcc 3.4.4でためして同一どういつ結果けっかがでたので、おそらくC++の仕様しようでないかとおもいます(現在げんざい調査ちょうさちゅうですが、ご存知ぞんじでしたら是非ぜひおしえください)。なお(4)のケースでA::operator=(A)をばれないようにするためには、B::operator=(B)なる関数かんすう定義ていぎする必要ひつようがありました。もし、さらにふか継承けいしょう関係かんけいがある場合ばあいには、おやクラス::operator=(おやクラス)でクラス=クラスを捕捉ほそくすることも可能かのうです。

以上いじょうのような検証けんしょうすえ、できあがったのが行列ぎょうれつライブラリmatrix.h(1.28)です。まだまだ改良かいりょう余地よちがありそうですが、よろしければ使つかってみてください。

19:59 fenrir が投稿とうこう : 固定こていリンク | | このエントリーを含むはてなブックマーク | トラックバック
このエントリーのトラックバックURL: https://fenrir.naruoka.org/mt/mt-tb.cgi/624
コメント

どうもご無沙汰ぶさたです。ベンチプレスががらなくなったホークスファンです。(わかるかな)
別件べっけんでググっていたところ奇跡きせきてきにたどりいたのでふとんでみました。

さて本題ほんだいですが、これはシンプルに B::operator=(B) が自動じどう生成せいせいされて、そのなかで A::operator=(A) がばれているのではないかとおもいました。
ま、勘違かんちがいだったらなかったことにしてください。

では仕事しごとにもどるとします。

Posted by: soleus : April 11, 2008 03:18 PM

>soleusさん、あらためTさん
ひさしぶりです。お元気げんきですか? こちらのほう今年度こんねんどはいってからジムの年間ねんかんパスを仲間なかまえてうれしいかぎりです。でもあの記録きろくやぶれるひとはでそうにないのでご安心あんしんください(わらい)
んで本題ほんだいですが、たしかにそうかんがえると納得なっとくがいきますね。そうするとサブクラスない自動じどう生成せいせいされるoperatpor=は、サブクラスない定義ていぎされたデータメンバのみのコピー、ならびにおやクラスのoperator=をしている、とかんがえられますね。ありがとうございます。

Posted by: fenrir : April 12, 2008 10:37 AM

代入だいにゅう演算えんざんのオーバーロードについて調しらべていて辿たどきました。
派生はせいクラスでオーバーライドする場合ばあい通常つうじょう基底きていクラスのメソッドはvirtualにするとおもいます。
記事きじのサンプルコードでは、基底きていクラスでvirtualになっておらず、べつメソッドとして実装じっそうされているためそのような動作どうさになっているとおもいます。
こちらでためしたところ、基底きていクラスでvirtual指定していすると、(4)のケースでoperator=(B)がばれました。

以上いじょうになりましたのでコメントしました。

Posted by: とおりすがり : February 24, 2009 12:22 PM

>とおりすがりさん
コメントありがとうございます。『virtualがない』ということで、なるほど、とおもいこちらても追試ついしをしてみました。しかしながら結果けっかわらず、(4)のケースでもA::operator=(A)がばれてしまいました(なおダウンロードできるコードにならってself_tについてはclass Aでtypedef A self_t;、class Bではtypedef B self_t;としています)。ためした環境かんきょうはVisual Studio Express Edition 2008とgcc 3.4.4についてです。よろしければテストされた環境かんきょうおしえていただけないでしょうか。

Posted by: fenrir : February 25, 2009 09:05 AM

こちらにアップされているサンプルコードをてみました。
そしてかったのは、class Aとclass Bで使用しようされているself_tのかたがclass AではAでclass Bでは Bになっています。これだとコンパイルべつメソッドとなされるのでオーバーライドされないですね。
class BでももどをAにしてやるとオーバーライドされるとおもいます。

Posted by: とおりすがり : February 25, 2009 12:28 PM

>とおりすがりさん
おっしゃるとおりだとおもいます、お手数てすうおかけしています。
しかしここではB::operator=のもどかたはBであってしいという都合つごうがありまして…。たとえばsome_functionがclass Bのみで定義ていぎされていて、(b=b_another).some_function()、および(b=a).some_function()(ちゅう:こちらは内部ないぶてき明示めいじアップキャストをすることによって処理しょり)をする場合ばあいかんがえられます。大人おとなしくB::operator=(B)関数かんすう定義ていぎしておけば解決かいけつするのですが、ぼく感覚かんかくすこしずれているのか、気持きもわるいなぁ、というのがこの記事きじ内容ないようでした。

Posted by: fenrir : February 25, 2009 03:21 PM

なるほど。
もともとオーバーライドしていたわけではなかったのですね。

もうおきのようですが、
うえでsoleusさんがわれているように
デフォルトではA::operator=(A)とB::operator=(B)がつくられます。
そしてb = bをした場合ばあい、A::operator=(A)とB::operator=(B)の両方りょうほうばれます。
これは、「A::operator=(A)だけがばれているわけではない」
ということです。
※A::operator=(A)がばれないと、Aのメンバは代入だいにゅうされませんよね?B::operator=(B)ではBのメンバのみ代入だいにゅうされます。

サンプルコードでは、たまたまA::operator=(A)のみオーバーロードしていたため、こちらに気付きづいただけで、fenrirさんの感覚かんかくとくにずれているわけではありません。B::operator=(B)もちゃんとばれています^^
b = bでaの参照さんしょう最終さいしゅうてきかえるわけではないので、(b = b)としてもちゃんとclass Bのメソッドは使つかえるはずです。

ちなみにB::operator=(A)は自動じどうつくられないので、自分じぶん定義ていぎしないかぎばれません。
未定義みていぎ状態じょうたいでb = aをするとコンパイルがとおりません。BはAですが、AはBではありません。

少々しょうしょうくどい説明せつめいになってしまいもうわけありません(あせ

Posted by: とおりすがり : February 26, 2009 12:58 AM
コメントする









名前なまえ、アドレスを登録とうろくしますか?
(次回じかい以降いこうコメント入力にゅうりょくらくになります)
  • 匿名とくめいでのコメントはけておりません。
  • 名前なまえ(ハンドルめい)とメールアドレスはかなら入力にゅうりょくしてください。
  • メールアドレスを表示ひょうじされたくないときはURLもかなら記入きにゅうしてください。
  • コメントらんでHTMLタグは使用しようできません。
  • コメント本文ほんぶん日本語にほんご(全角ぜんかく文字もじ)がある程度ていどおおふくまれている必要ひつようがあります。
  • コメントらんないのURLとおもわれる文字もじれつ自動的じどうてきにリンクに変換へんかんされます。