19

c++0x の右辺値参照を利用するために新しい演算子のオーバーロードを追加していますが、多くの冗長なコードを生成しているように感じます。

treedouble 値に対する代数演算のツリーを保持するクラス があります。使用例を次に示します。

tree x = 1.23;
tree y = 8.19;
tree z = (x + y)/67.31 - 3.15*y;
...
std::cout << z; // prints "(1.23 + 8.19)/67.31 - 3.15*8.19"

各二項演算 (plus など) では、各辺は lvalue tree、 rvalue tree、またはのいずれかになりますdouble。これにより、2 項演算ごとに 8 つのオーバーロードが発生します。

// core rvalue overloads for plus:
tree operator +(const tree& a, const tree& b);
tree operator +(const tree& a, tree&&      b);
tree operator +(tree&&      a, const tree& b);
tree operator +(tree&&      a, tree&&      b);

// cast and forward cases:
tree operator +(const tree& a, double      b) { return a + tree(b); }
tree operator +(double      a, const tree& b) { return tree(a) + b; }
tree operator +(tree&&      a, double      b) { return std::move(a) + tree(b); }
tree operator +(double      a, tree&&      b) { return tree(a) + std::move(b); }

// 8 more overloads for minus

// 8 more overloads for multiply

// 8 more overloads for divide

// etc

また、バイナリ演算 (マイナス、乗算、除算など) ごとに繰り返す必要があります。

ご覧のとおり、実際に記述する必要がある関数は 4 つだけです。他の 4 つはキャストしてコア ケースに転送できます。

このコードのサイズを小さくするための提案はありますか?

PS:このクラスは実際には単なる double のツリーよりも複雑です。コピーを減らすと、プロジェクトのパフォーマンスが劇的に向上します。したがって、余分なコードがあっても、右辺値のオーバーロードは私にとって価値があります。上記の「キャストアンドフォワード」のケースをテンプレート化する方法があるのではないかと疑っていますが、何も考えられないようです。

4

4 に答える 4

7

ちょっと遅い答え:問題のクラスが移動可能である場合、移動は非常に安価であり、可能であれば常にすべての引数から移動する場合、引数を値で渡すことはオプションかもしれません:

tree operator +(tree      a, tree      b);

ツリーが移動可能で、右辺値参照が実引数として渡される場合、関数への引数は可能な場合はツリーの移動コンストラクターで初期化され、それ以外の場合はコピー コンストラクターで初期化されます。次に、関数は適切な方法で引数を使用して必要なことを実行できます (たとえば、内部を移動するなど)。

多くのオーバーロード バージョンと比較して、右辺値参照引数を渡すときに余分な移動が発生しますが、一般的には優れていると思います。

また、IMO、tree &&引数は一時コピーを介して左辺値を受け入れる必要があるかもしれませんが、これは現在のコンパイラが行っていることではないため、あまり役に立ちません。

于 2010-05-01T08:17:13.780 に答える
4

まず、operator+ が引数を変更する理由がまったくわかりません (これは典型的な不変バイナリ ツリーの実装ではありません)。そのため、r 値参照と l 値参照に違いはありません。しかし、サブツリーには親へのポインタなどがあると仮定しましょう。

あなたが示した使用例から、 double から tree への暗黙的な変換があるようです。その場合、「キャスト アンド フォワード」ケースは必要ありません。コンパイラはユーザー定義の変換を見つけます。

移動以外のオーバーロードは、新しいツリーに入る新しいインスタンスを作成することになりませんか? もしそうなら、残りの 4 つのケースのうち 3 つをフォワーダーとして書いていただけると思います。

tree operator +(tree&& a, tree&& b); // core case
tree operator +(tree   a, tree   b) { return std::move(a) + std::move(b); }
tree operator +(tree   a, tree&& b) { return std::move(a) + std::move(b); }
tree operator +(tree&& a, tree   b) { return std::move(a) + std::move(b); }

もちろん、マクロを使用して、各オペレーターの 3 つ (または 7 つ) の転送バージョンを生成できます。

編集:これらの呼び出しがあいまいであるか、再帰に解決される場合は、どうですか:

tree add_core(tree&& a, tree&& b);
tree operator +(tree&& a, tree&& b) { return add_core(std::move(a), std::move(b)); }
tree operator +(tree   a, tree   b) { return add_core(std::move(a), std::move(b)); }
tree operator +(tree   a, tree&& b) { return add_core(std::move(a), std::move(b)); }
tree operator +(tree&& a, tree   b) { return add_core(std::move(a), std::move(b)); }

編集:暗黙の変換を使用しない演算子の失敗の再現:

#include <iostream>

template<typename T>
class tree;

template<typename T> tree<T> add(tree<T> a, tree<T> b)
{
    std::cout << "added!" << std::endl << std::endl;
    return tree<T>();
}

template<typename T> tree<T> operator +(tree<T>   a, tree<T>   b) { return add(a, b); }

template<typename T>
class tree
{
public:
    tree() { }
    tree(const tree& t) { std::cout << "copy!" << std::endl; }
    tree(double val)    { std::cout << "double" << std::endl; }
    friend tree operator +<T>(tree a, tree b);
};

int main()
{
    tree<double>(1.0) + 2.0;
    return 0;
}

暗黙的な変換が機能するテンプレートのないバージョン:

#include <iostream>

class tree
{
public:
    tree() { }
    tree(const tree& t) { std::cout << "copy!" << std::endl; }
    tree(double val)    { std::cout << "double" << std::endl; }
friend tree operator +(tree a, tree b);
};

tree add(tree a, tree b)
{
    std::cout << "added!" << std::endl << std::endl;
    return tree();
}

tree operator +(tree a, tree b) { return add(a, b); }

int main()
{
    tree(1.0) + 2.0;
    return 0;
}
于 2010-04-23T04:26:51.620 に答える
3

プライマリユニットとして左辺値または右辺値をオーバーロードする必要がないように、それらをメンバー関数として定義することになっています(とにかく不要です)つまり、

class Tree {
    Tree operator+ const (const Tree&);
    Tree operator+ const (Tree&&);
};

最初の l または r の値は無関係だからです。さらに、そのコンストラクターが使用可能な場合、コンパイラーは自動的に構築します。ツリーが double から構築される場合、ここで自動的に double を使用でき、double は適切に右辺値になります。これはたった2つの方法です。

于 2010-04-26T18:08:43.627 に答える
1

問題は、非 const パラメーターを使用して操作を定義したことだと思います。あなたが定義する場合

tree operator +(const tree& a, const tree& b);

右辺値参照と左辺値参照に違いはないので、定義する必要もありません

tree operator +(tree&&      a, const tree& b);

さらに、考えることができるように double が tree に変換可能である場合、tree x = 1.23;どちらも定義する必要はありません

tree operator +(double      a, const tree& b){ return tree(a) + b; }

コンパイラが作業を行います。

operator+ がツリーパラメーターを値で受け取る場合は、右辺値と左辺値の違いを確認する必要があります

tree operator +(tree a, tree b);
于 2010-04-23T17:07:47.053 に答える