web-dev-qa-db-ja.com

クラスの外で演算子+または+ =を定義する理由と、それを適切に行う方法は?

私は間の違いについて少し混乱しています

Type  operator +  (const Type &type);
Type &operator += (const Type &type);

そして

friend Type  operator +  (const Type &type1, const Type &type2);
friend Type &operator += (const Type &type1, const Type &type2);

どちらの方法が好ましいですか、それらはどのように見え、いつどちらを使用する必要がありますか?

27
Dave

演算子の最初の形式は、クラスType内で定義するものです。

演算子の2番目の形式は、クラスTypeと同じ名前空間で独立した関数として定義するものです。

独立した関数を定義することは非常に良い考えです。なぜなら、それらのオペランドは暗黙の変換に参加できるからです。

このクラスを想定します。

_class Type {
    public:
    Type(int foo) { }

    // Added the const qualifier as an update: see end of answer
    Type operator + (const Type& type) const { return *this; }
};
_

次に、次のように書くことができます。

_Type a = Type(1) + Type(2); // OK
Type b = Type(1) + 2; // Also OK: conversion of int(2) to Type
_

しかし、あなたは書くことができませんでした:

_Type c = 1 + Type(2); // DOES NOT COMPILE
_

_operator+_を無料の関数として使用すると、最後のケースも可能になります。

ただし、演​​算子の2番目の形式が間違っているのは、オペランドのプライベートメンバーを直接微調整することによって加算を実行することです(そうでなければ、フレンドである必要はないと思います)。 notはそれを行うべきではありません:代わりに、演算子もクラス内で定義されるべきであり、独立した関数はそれらを呼び出すべきです。

それがどうなるかを見るために、教祖のサービスを求めましょう: http://www.gotw.ca/gotw/004.htm 。最後にスクロールして、独立した機能を実装する方法を確認します。

更新:

James McNellisが彼のコメントで述べているように、与えられた2つの形式にも別の違いがあります。最初のバージョンでは、左側がconst-qualifiedではありません。 _operator+_のオペランドは、実際には加算の一部として変更すべきではないため、常にconst-qualifyすることをお勧めします。私の例のクラスTypeはこれを実行しますが、最初は実行しませんでした。

結論

演算子_+_および_+=_を処理する最良の方法は次のとおりです。

  1. クラス内で_operator+=_をT& T::operator+=(const T&);として定義します。これは、追加が実装される場所です。
  2. クラス内で_operator+_をT T::operator+(const T&) const;として定義します。この演算子は、前の演算子の観点から実装されます。
  3. クラスの外部で、同じ名前空間の内部に無料の関数T operator+(const T&, const T&);を提供します。この関数は、メンバー_operator+_を呼び出して作業を行います。

手順2を省略して、無料の関数で_T::operator+=_を直接呼び出すこともできますが、個人的な好みとして、すべての加算ロジックをクラス内に保持したいと思います。

29
Jon

C++ 03およびC++ 0x( [〜#〜] nrvo [〜#〜] およびmove-semantics)に関して、演算子を実装する適切な方法は次のとおりです。

struct foo
{
    // mutates left-operand => member-function
    foo& operator+=(const foo& other)
    {
        x += other.x;

        return *this;
    }

    int x;
};

// non-mutating => non-member function
foo operator+(foo first, // parameter as value, move-construct (or elide)
                const foo& second) 
{
    first += second; // implement in terms of mutating operator

    return first; // NRVO (or move-construct)
}

上記を次のように組み合わせたくなることに注意してください。

foo operator+(foo first, const foo& second) 
{
    return first += second;
}

しかし、(私のテストでは)コンパイラがNRVOを有効にしない(またはセマンティクスを移動する)ことがあります。これは、(ミューテーション演算子をインライン化するまで)first += secondfirstと同じです。より簡単で安全なのは、それを分割することです。

5
GManNickG

friend指定子は、宣言されているものがクラスのメンバーではないが、その仕事をするためにクラスのインスタンスのプライベートメンバーにアクセスする必要がある場合に使用されます。

演算子がクラス自体で定義される場合は、最初の方法を使用してください。スタンドアロン関数の場合は、2番目の関数を使用します。

0
cHao