アプリ版:「スタンプのみでお礼する」機能のリリースについて

仮想基底クラスをもつクラスの代入演算で、仮想基底クラスの代入が各々のサブオブジェクトにつきただ一回なされるようにしたい場合に、みなさん、どのように設計されていますか?
何か、手軽なテクニックがあれば、ご教示ください。

===(コンストラクタなどは略、アクセス制御も略)
たとえば、
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 が正解なんでしょうけど^^

A 回答 (1件)

コピー代入演算子は、コピーコンストラクタを用いて実装するのが基本です。

ですから、

C& C::operator=(const C& src)
{
 C temp(src);
 this->swap(temp);
 return *this;
}

のようにすれば、代入演算子の中で行われているのは、実は初期化ですので、コピーが一回しか発生しないことが保証されます。
なお、swapはオブジェクトの値を交換するためのメンバ関数で、原則として決して例外を送出しないように実装すべきものです。仮想継承にまつわる工夫(仮想基底クラスの部分オブジェクトの交換を1回しか行わないなど)は、このswapメンバ関数に集約しておくと後々楽です。

こうすることで、複数コピーされる問題が解消するほか、代入演算子の「強い例外安全保障」、そして「自己代入対策」も同時に得られます。
(データメンバがint型ならあまり関係ないでしょうが、一般的にはこれらは重要になります)

この回答への補足

そうかそうか。ブール値を 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; }
};

補足日時:2007/08/25 18:12
    • good
    • 0
この回答へのお礼

なるほど。アロケートしたメモリのポインタなどのガードには便利かもしれないですね。

swap() を定義するのに、仮想継承のオブジェクトの複数回の swap() を防止するのには、プログラマが注意していないとならないのですかね、やっぱり。。それに、コンストラクトしてスワップするので、2回分の代入処理量くらいになっちゃいますね^^

でも、興味あるテクニックです。ありがとうございます。

お礼日時:2007/08/25 17:18

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!