はい、オブジェクトのインターフェイスの一部が非メンバー関数で構成されていることを意味します。
そして、クラス T のオブジェクトに対して次の表記法を使用する必要があるという事実については、あなたの言うとおりです。
void T::doSomething(int value) ; // method
void doSomething(T & t, int value) ; // non-member non-friend function
doSomething 関数/メソッドが void を返し、「値」と呼ばれる int パラメータが必要な場合。
しかし、言及する価値があることが 2 つあります。
1 つ目は、クラスのインターフェイスの関数部分が同じ名前空間にある必要があることです。これは、オブジェクトとそのインターフェースの一部である関数を「まとめる」ためだけに、名前空間を使用するもう 1 つの理由です (別の理由が必要な場合)。
良い部分は、それが良いカプセル化を促進することです. しかし、悪い点は、関数のような表記法を使用していることです。個人的には、かなり嫌いです。
2 つ目は、オペレーターがこの制限を受けないことです。たとえば、クラス T の += 演算子は、次の 2 つの方法で記述できます。
T & operator += (T & lhs, const T & rhs) ;
{
// do something like lhs.value += rhs.value
return lhs ;
}
T & T::operator += (const T & rhs) ;
{
// do something like this->value += rhs.value
return *this ;
}
ただし、両方の表記法は次のように使用されます。
void doSomething(T & a, T & b)
{
a += b ;
}
これは、美的な観点から、関数のような表記法よりもはるかに優れています。
さて、同じインターフェイスから関数を記述でき、「.」を介して呼び出すことができるのは、非常にクールな構文糖衣です。michalmocny で言及されているように、C# のような記法。
編集:いくつかの例
何らかの理由で、2 つの「整数のような」クラスを作成したいとしましょう。1 つ目は IntegerMethod です。
class IntegerMethod
{
public :
IntegerMethod(const int p_iValue) : m_iValue(p_iValue) {}
int getValue() const { return this->m_iValue ; }
void setValue(const int p_iValue) { this->m_iValue = p_iValue ; }
IntegerMethod & operator += (const IntegerMethod & rhs)
{
this->m_iValue += rhs.getValue() ;
return *this ;
}
IntegerMethod operator + (const IntegerMethod & rhs) const
{
return IntegerMethod (this->m_iValue + rhs.getValue()) ;
}
std::string toString() const
{
std::stringstream oStr ;
oStr << this->m_iValue ;
return oStr.str() ;
}
private :
int m_iValue ;
} ;
このクラスには、その内部にアクセスできる 6 つのメソッドがあります。
2 つ目は IntegerFunction です。
class IntegerFunction
{
public :
IntegerFunction(const int p_iValue) : m_iValue(p_iValue) {}
int getValue() const { return this->m_iValue ; }
void setValue(const int p_iValue) { this->m_iValue = p_iValue ; }
private :
int m_iValue ;
} ;
IntegerFunction & operator += (IntegerFunction & lhs, const IntegerFunction & rhs)
{
lhs.setValue(lhs.getValue() + rhs.getValue()) ;
return lhs ;
}
IntegerFunction operator + (const IntegerFunction & lhs, const IntegerFunction & rhs)
{
return IntegerFunction(lhs.getValue() + rhs.getValue()) ;
}
std::string toString(const IntegerFunction & p_oInteger)
{
std::stringstream oStr ;
oStr << p_oInteger.getValue() ;
return oStr.str() ;
}
メソッドが 3 つしかないため、内部にアクセスできるコードの量が減ります。3つの非会員非友人機能があります。
2 つのクラスは次のように使用できます。
void doSomething()
{
{
IntegerMethod iMethod(25) ;
iMethod += 35 ;
std::cout << "iMethod : " << iMethod.toString() << std::endl ;
IntegerMethod result(0), lhs(10), rhs(20) ;
result = lhs + 20 ;
// result = 10 + rhs ; // WON'T COMPILE
result = 10 + 20 ;
result = lhs + rhs ;
}
{
IntegerFunction iFunction(125) ;
iFunction += 135 ;
std::cout << "iFunction : " << toString(iFunction) << std::endl ;
IntegerFunction result(0), lhs(10), rhs(20) ;
result = lhs + 20 ;
result = 10 + rhs ;
result = 10 + 20 ;
result = lhs + rhs ;
}
}
演算子の使用 ("+" と "+=") を比較すると、演算子をメンバーにするか非メンバーにするかによって、見かけ上の使用方法に違いがないことがわかります。それでも、次の 2 つの違いがあります。
メンバーはすべての内部にアクセスできます。非メンバーはパブリック メンバー メソッドを使用する必要があります
+、* などの一部の二項演算子から型昇格を行うのは興味深いことです。1 つのケース (つまり、上記の lhs 昇格) では、メンバー メソッドに対しては機能しないからです。
ここで、非演算子の使用 ("toString") を比較すると、メンバーの非演算子の使用は、非メンバー関数よりも Java ライクな開発者にとってより「自然」であることがわかります。このような不慣れな点にもかかわらず、C++ の場合、その構文にもかかわらず、クラス内部にアクセスできないため、OOP の観点から非メンバー バージョンの方が優れていることを受け入れることが重要です。
おまけとして: 何も持たないオブジェクト (たとえば、<windows.h> の GUID 構造) に演算子 (または非演算子関数) を追加したい場合は、構造そのもの。演算子の場合、構文は自然になり、演算子以外の場合は...
免責事項: もちろん、これらのクラスはばかげています: set/getValue は、その内部にほとんど直接アクセスします。しかし、 Monoliths "Unstrung"でHerb Sutter によって提案されているように、整数を文字列に置き換えると、より現実的なケースが表示されます。