• 締切済み

仮想基底クラスをもつクラスの代入演算

仮想基底クラスをもつクラスの代入演算で、仮想基底クラスの代入が各々のサブオブジェクトにつきただ一回なされるようにしたい場合に、みなさん、どのように設計されていますか? 何か、手軽なテクニックがあれば、ご教示ください。 ===(コンストラクタなどは略、アクセス制御も略) たとえば、 struct X { int x; }; struct A : virtual X { int a; }; struct B : virtual X { int b; }; struct C : A, B { int c; }; int main() { C c1, c2; c1 = c2; } として、デフォルト代入に頼ると、c1.x への c2.x の代入を複数回行うコンパイラが多いですよね(というか、きっちり1回のみ行うようにしているコンパイラに出会ったことがない)。C++標準でも、初期化はただ一度とコンパイラが保証するようになっていますが、代入は何度代入してもいいようになっていたと思います。 以下のように、各クラスで、直接の非仮想基底クラスと自身のデータメンバにのみ代入するメンバ関数を定義して、最派生クラスでのみ、仮想基底クラスの代入を行うのが、普通のやり方なんでしょうか。 これでもいいのですが、後に、ある基底クラスに仮想クラスが付け加わったりすると、その基底クラスから派生しているクラスの代入演算の定義をすべていじらないといけなくなるので、もっといいテクニックがあるのなら、と思いまして質問したしだいです。 struct X { int x; X &nvAssign(const X &z) { x = z.x; return *this; } X &operator=(const X &z) { nvAssign(z); } }; struct A : virtual X { int a; A &nvAssign(const A &z) { a = z.a; return *this; } A &operator=(const A &z) { X::nvAssign(z); return nvAssign(z); } }; struct B : virtual X { int b; B &nvAssign(const B &z) { b = z.b; return *this; } B &operator=(const B &z) { X::nvAssign(z); return nvAssign(z); } }; struct C : A, B { int c; C &nvAssign(const C &z) { A::nvAssign(z); B::nvAssign(z); c = z.c; return *this; } C &operator=(const C &z) { X::nvAssign(z); return nvAssign(z); } }; === 1. 仮想基底クラスは何度代入されてもいいようにするもんだ。 2. 仮想基底クラスにデータメンバを持たせるのは間違いだ。 3. 遅くなるから、そもそも仮想基底クラスなど使わない。 という回答は無しでお願いいたします。たぶん、1 か 2 が正解なんでしょうけど^^

みんなの回答

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.1

コピー代入演算子は、コピーコンストラクタを用いて実装するのが基本です。ですから、 C& C::operator=(const C& src) {  C temp(src);  this->swap(temp);  return *this; } のようにすれば、代入演算子の中で行われているのは、実は初期化ですので、コピーが一回しか発生しないことが保証されます。 なお、swapはオブジェクトの値を交換するためのメンバ関数で、原則として決して例外を送出しないように実装すべきものです。仮想継承にまつわる工夫(仮想基底クラスの部分オブジェクトの交換を1回しか行わないなど)は、このswapメンバ関数に集約しておくと後々楽です。 こうすることで、複数コピーされる問題が解消するほか、代入演算子の「強い例外安全保障」、そして「自己代入対策」も同時に得られます。 (データメンバがint型ならあまり関係ないでしょうが、一般的にはこれらは重要になります)

mikaemi
質問者

お礼

なるほど。アロケートしたメモリのポインタなどのガードには便利かもしれないですね。 swap() を定義するのに、仮想継承のオブジェクトの複数回の swap() を防止するのには、プログラマが注意していないとならないのですかね、やっぱり。。それに、コンストラクトしてスワップするので、2回分の代入処理量くらいになっちゃいますね^^ でも、興味あるテクニックです。ありがとうございます。

mikaemi
質問者

補足

そうかそうか。ブール値を swap() につけておけば、あまり仮想基底を気にしないですみますね。代償は、処理量が増えることですが。。。。でも、まあ、jacta さんが提示してくれたテクニックでプログラミングは楽になります^^ ありがとうございました。 ==== #include <iostream> #include <algorithm> struct X { int x; X() {} X(const X &z) { x = z.x; } void swap(X &z, bool top) throw() { std::swap(x, z.x); } X &operator=(const X &z) { X t(z); swap(t, true); return *this; } }; struct A : virtual X { int a; A() {} A(const A &z) : X(z) { a = z.a; } void swap(A &z, bool top) throw() { if (top) X::swap(z, false); std::swap(a, z.a); } A &operator=(const A &z) { A t(z); swap(t, true); return *this; } }; struct B : virtual X { int b; B() {} B(const B &z) : X(z) { b = z.b; } void swap(B &z, bool top) throw() { if (top) X::swap(z, false); std::swap(b, z.b); } B &operator=(const B &z) { B t(z); swap(t, true); return *this; } }; struct C : A, B { int c; C() {} C(const C &z) : X(z), A(z), B(z) { c = z.c; } void swap(C &z, bool top) throw() { if (top) X::swap(z, false); A::swap(z, false); B::swap(z, false); std::swap(c, z.c); } C &operator=(const C &z) { C t(z); swap(t, true); return *this; } };

関連するQ&A

  • 仮想基底クラスをもつクラスの代入演算2

    <仮想基底クラスをもつクラスの代入演算>の続きですが、結局、assign のようなものを作って、以下のように書くのが一番いいのでしょうか。。。 ====(一応、自己代入も考慮して書くと^^) #include <algorithm> struct X { int x; X &assign(const X &z, bool top) { if (this != &z) x = z.x; return *this; } X &operator=(const X &z) { return assign(z, true); } }; struct A : virtual X { int a; A &assign(const A &z, bool top) { if (this != &z) { if (top) X::assign(z, false); a = z.a; } return *this; } A &operator=(const A &z) { return assign(z, true); } }; struct B : virtual X { int b; B &assign(const B &z, bool top) { if (this != &z) { if (top) X::assign(z, false); b = z.b; } return *this; } B &operator=(const B &z) { return assign(z, true); } }; struct C : A, B { int c; C &assign(const C &z, bool top) { if (this != &z) { if (top) X::assign(z, false); A::assign(z, false); B::assign(z, false); c = z.c; } return *this; } C &operator=(const C &z) { return assign(z, true); } }; ==== ただ、<仮想基底クラスをもつクラスの代入演算>で、jacta さんが提示してくださった方法も、処理量は増えますが、アロケートする必要があるポインタなどのデータメンバのアロケートやデリートする処理コードをコンストラクタに集中できるので、「コーディング量の減少・改変の際の修正し忘れの可能性を低くできる」というメリットがあると思います。ハンドルクラスやコンテナクラスを実装する際は、swap() 機能が必要となると考えられるので、jacta さんのコピーコンストラクタでコピー代入を実装する方法の方がいいのかとも思います。 みなさん、どう思われますか? また、そのほかに、効果的なテクニックがあれば、ぜひ、ご教示ください。

  • 派生クラスのメンバを基底クラスの参照に代入(C++

    文末のコードのように、 基底クラスで、派生クラスのメンバの参照を持つのはまずいでしょうか。 (classではなくstructにしているのは質問上でのpublic:の省略のためだけです) 初期化順序的には、基底クラスの参照先は、 基底クラスのコンストラクタが走る時点で初期化されていないので、 コンストラクタ内で参照に対して何かしようとすると問題になると思っています。 基底クラスのコンストラクタ内で派生クラスメンバの参照に対して何かしなければ、 参照は有効で、派生クラスのコンストラクタ実行後であれば 問題なく動くと思ってよいでしょうか。 struct A { int& m_ref; A(int& ref) : m_ref(ref) { } }; struct B : public A { int m_obj; B() : A(m_obj) { } };

  • テンプレート仮想関数のようなもの

    仮想関数をテンプレートにできないのは仕様と理解しているのですが、多少複雑になっても、それぞれの型に対するメンバ関数をいちいち記述していくことを回避するようなテクニックはありませんか? struct HOGE{ //template<typename T> //virtual operator T() = 0; //上記ができないので以下を全て記入していかなければならない virtual operator int() = 0; virtual operator short() = 0; //... }; struct HOGE1 : HOGE{ //template<typename T> //operator T() overtride{return T();} //上記ができないので以下を全て記入していかなければならない operator int(){return 0;} operator short(){return 0;} //... };

  • operator代入演算子のやり方で疑問が

    C++の勉強をしています。 そこでoperatorを使うことをやっているんですが、この演算子、引数を二つつけるとエラーになります。クラス内で定義するとこうなります。 ですがグローバルで定義するとエラーが出力されません。 何故なんでしょうか? 戻り値の関係でしょうか?  class Complex { public: Complex() { } public: // これはエラー、この演算子関数のパラメータが多すぎますと出力される const Complex operator+(const Complex x, const Complex y) { } }; // これはでない・・・ const Complex operator+(const Complex x, const Complex y) { } 上記のプログラムだと、下に書いてある代入演算子は問題ありません。 なぜこうなるんでしょうか? ご教授お願いします。

  • C++で継承元のクラスの代入演算子を呼び出す方法

    C++であるクラス継承したクラスの代入演算子で 継承元のクラスの代入演算子を呼び出す方法がわかりません。 こんな感じのソースです。 class T { T& operator=(const T &t) { } } class U :: T{ U& operator=(const U &u) { // ここでT.operator=()の代入演算を実行したい } } ((T)(*this)).operator=((T)(u))のようにキャストすればいけるかと思ったんですが、 コンパイルエラーでした。 よろしくお願いします。 また、これはプログラム上好ましくない手法でしたら、 別な実現方法をお教えください。

  • C++テンプレートクラスの内部クラスについて

    テンプレートクラスについていろいろ試していたところ以下のようなコードで struct A {     struct AA { };     operator A::AA() { return A::AA(); } // (1) }; template<typename T> struct B {     struct BB { };     template<typename U>     operator B<U>() { return B<U>(); } // (2)     template<typename U>     operator typename B<U>::BB() { return typename B<U>::BB(); } // (3) }; int main() {     static_cast<A::AA>(A()); // (1) ok     static_cast<B<int> >(B<short>()); // (2) ok     static_cast<B<int>::BB>(B<short>()); // (3) compile error     return 0; } (1)と(2)はできて(3)だけがコンパイルを通りませんでした。 試したコンパイラはVC9とg++(3.3.4)とbcc32(5.5.1)で、VC9では以下のようなエラーをはきました。 「error C2440: 'static_cast' : 'B<T>' から 'B<T>::BB' に変換できません。     with [ T=short ] and [ T=int ] コンストラクタはソース型を持てません、またはコンストラクタのオーバーロードの解決があいまいです。」 (1)と(2)ができれば(3)のようなこともできそうな感じがしたのですが、他に書き方があるのでしょうか。 どなたかご存知の方がいらっしゃいましたらご教示お願いします。

  • 構造体の要素すべてに対する四則演算の方法を教えてください.

    構造体の要素すべてに対する四則演算の方法を教えてください. たとえば、 2点a,bの座標成分x,y,zをそれぞれの座標ごとに足す方法を教えてください. 下のようにx,y,z成分を持ったa,bがあります。 struct point{ int x; int y; int z; }; struct point a; struct point b; struct point c; この場合, c=a+b; と書くことができず, それぞれの成分ごとに以下のように足さなくてはなりません. c.x=a.x+b.x; c.y=a.y+b.y; c.z=a.z+b.z; この方法でできるのですが, 非常に効率的でないのでなにかもっと簡単に記述する方法を教えてください. お願いいたします.

  • 基底

    a=(0,1,1) b=(1,0,1) c=(1,1,0) である。X=t(x,y,z)の、基底a,b,cに対する座標ベクトルを求めてくださいお願いします。(ちなみにt()は転置行列を表しています)

  • 演算子のオーバーロードについて

    []演算子のオーバーロードでつまづきました。 代入文で左辺、右辺、両方ともに[]演算子を使う場合 int &operator[](int i) { return a[i]; } int &operator[]のようにして、戻り値を参照型にしています。 このとき、main関数内で、 ob1[2]=ob2[2];(ob1,ob2はoperator[]関数が関連付けられているクラスのオブジェクト) のようにすると、左辺(ob1)にちゃんと代入されています。 つまりこの場合、main()関数内で、 (int &)型の(this->a[i])(元のオブジェクトは、ob1) に (int &)型の(this->a[i])(元のオブジェクトは、ob2)を代入しているのでしょうか?

  • C言語の演算について

    次のプログラムを実行したらどう出力されますか。 微妙な代入演算の違いが分からないので、教えていただけないでしょうか。 #include<stdio.h> void main (void) { int x = 5; int y = 8; int z = 3; int a,b,c,d,e,f; a = y == x + z; b = !x; c = x + y / z; d = x *=z - 1; e = --y / --z; f = y+++ % x++; printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f); } できれば途中のトレースも書いていただけると助かります。 よろしくお願いします。

専門家に質問してみよう