5

メンバー関数と同じ「ドット」表記を使用して、オブジェクトで非メンバー非フレンド関数を使用する方法はありますか?

(任意の) メンバーをクラスから取り出して、ユーザーにいつもと同じように使用させることはできますか?

より長い説明:

Scott Meyersや Herb Sutter などは、非メンバー非フレンド関数はオブジェクトのインターフェイスの一部であり、カプセル化を改善できると主張しています。私は彼らに同意します。

しかし、最近この記事を読んだ後: http://www.gotw.ca/gotw/084.htm構文の意味に疑問を感じています。

その記事で、Herb は、単一insertの 、erase、およびreplaceメンバーと、同じ名前の複数の非メンバー非フレンド関数を持つことを提案しています。

これは、私が思うように、いくつかの関数はドット表記で使用し、他の関数はグローバル関数として使用する必要があると Herb が考えていることを意味するのでしょうか?

std::string s("foobar");

s.insert( ... ); /* One like this */
insert( s , ...); /* Others like this */

編集:

非常に有益な回答をありがとうございますが、私の質問のポイントが見落とされていると思います。

演算子の特定のケースと、演算子が「自然な」表記をどのように保持するかについては特に言及しませんでした。また、すべてを名前空間にラップする必要もありません。これらのことは、私がリンクした記事に書かれています。

質問自体は次のとおりでした。

記事の中で、Herb は、1 つの insert() メソッドをメンバーにし、残りを非メンバー、非フレンド関数にすることを提案しています。

これは、insert() の 1 つの形式を使用するにはドット表記を使用する必要があり、他の形式では使用しないことを意味します。

それは私だけですか、それともクレイジーに聞こえますか?

おそらく単一の構文を使用できると思います。(Boost::function が mem_fun の *this パラメーターを取る方法を考えています)。

4

7 に答える 7

4

はい、オブジェクトのインターフェイスの一部が非メンバー関数で構成されていることを意味します。

そして、クラス 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. メンバーはすべての内部にアクセスできます。非メンバーはパブリック メンバー メソッドを使用する必要があります

  2. +、* などの一部の二項演算子から型昇格を行うのは興味深いことです。1 つのケース (つまり、上記の lhs 昇格) では、メンバー メソッドに対しては機能しないからです。

ここで、非演算子の使用 ("toString") を比較すると、メンバーの非演算子の使用は、非メンバー関数よりも Java ライクな開発者にとってより「自然」であることがわかります。このような不慣れな点にもかかわらず、C++ の場合、その構文にもかかわらず、クラス内部にアクセスできないため、OOP の観点から非メンバー バージョンの方が優れていることを受け入れることが重要です。

おまけとして: 何も持たないオブジェクト (たとえば、<windows.h> の GUID 構造) に演算子 (または非演算子関数) を追加したい場合は、構造そのもの。演算子の場合、構文は自然になり、演算子以外の場合は...

免責事項: もちろん、これらのクラスはばかげています: set/getValue は、その内部にほとんど直接アクセスします。しかし、 Monoliths "Unstrung"でHerb Sutter によって提案されているように、整数を文字列に置き換えると、より現実的なケースが表示されます。

于 2008-12-01T22:53:56.207 に答える
1

個人的には、無料機能の拡張性が好きです。関数はこのsizeための優れた例です。

// joe writes this container class:
namespace mylib {
    class container { 
        // ... loads of stuff ...
    public:
        std::size_t size() const { 
            // do something and return
        }
    };

    std::size_t size(container const& c) {
        return c.size();
    } 
}

// another programmer decides to write another container...
namespace bar {
    class container {
        // again, lots of stuff...
    public:
        std::size_t size() const {
            // do something and return
        }
    };

    std::size_t size(container const& c) {
        return c.size();
    } 
}

// we want to get the size of arrays too
template<typename T, std::size_t n>
std::size_t size(T (&)[n]) {
    return n;
}

ここで、フリーサイズ関数を使用するコードについて考えてみます。

int main() {
    mylib::container c;
    std::size_t c_size = size(c);

    char data[] = "some string";
    std::size_t data_size = size(data);
}

ご覧のとおりsize(object)、型が含まれている名前空間を気にすることなく(引数の型に応じて、コンパイラは名前空間自体を把握します)、舞台裏で何が起こっているかを気にすることなく、使用できます。beginlikeとendasをfree関数として使用することも検討してください。これもまさにboost::range そうです。

于 2008-12-02T01:47:46.887 に答える
1

しかし、最近この記事を読んだ後: http://www.gotw.ca/gotw/084.htm構文の意味に疑問を感じています。

構文への影響は、適切に作成された C++ ライブラリのいたるところで見られるものです。C++ は至る所でフリー関数を使用しています。これは、OOP のバックグラウンドを持つ人々にとっては珍しいことですが、C++ でのベスト プラクティスです。例として、STL ヘッダーを考えてみましょう<algorithm>

したがって、ドット表記の使用は規則の例外となり、その逆ではありません。

他の言語は他の方法を選択することに注意してください。これにより、C# と VB に「拡張メソッド」が導入され、静的関数のメソッド呼び出し構文をエミュレートできるようになりました (つまり、まさにあなたが考えていたものです)。繰り返しになりますが、C# と VB は厳密にオブジェクト指向の言語であるため、メソッド呼び出しの表記を 1 つにすることがより重要になる場合があります。

それとは別に、関数は常に名前空間に属します – 私自身は時々この規則に違反します (ただしmain.cpp、これが役割を果たさない 1 つのコンパイル単位、つまり に相当するものでのみ)。

于 2008-12-01T23:08:26.350 に答える
1

非メンバー非フレンドをドット表記、つまり「オペレーター」で表記する方法はありません。オーバーロードすることはできません。

匿名の名前空間 (現在の翻訳単位のみが関数を必要とする場合) またはユーザーにとって意味のある名前空間のいずれかで、非メンバーの非フレンド クラスを常にラップする必要があります。

于 2008-12-01T22:39:57.327 に答える
1

単一の構文を使用できますが、好みの構文ではない可能性があります。クラス スコープ内に 1 つの insert() を配置する代わりに、それをクラスのフレンドにします。今、あなたは書くことができます

mystring s;
insert(s, "hello");
insert(s, other_s.begin(), other_s.end());
insert(s, 10, '.');

非仮想のパブリック メソッドの場合、非メンバーのフレンド関数として定義するのと同じです。ドット/ドットなしの混合構文が気になる場合は、必ずそれらのメソッドを代わりにフレンド関数にしてください。違いはありません。

将来的には、このような多態的な関数を書くこともできるようになるでしょう。おそらくこれは、人為的にフリー関数をドット構文に強制しようとするのではなく、C++ の方法です。

于 2008-12-02T08:31:12.970 に答える
1

ドット表記を保持するだけでなく、クラス外のフレンドである必要のない関数も分離したい場合 (プライベート メンバーにアクセスできず、カプセル化が壊れる)、おそらく mixin クラスを作成できます。ミックスインで「通常の」挿入を純粋な仮想にするか、非仮想のままにして CRTP を使用します。

template<typename DERIVED, typename T>
struct OtherInsertFunctions {
    void insertUpsideDown(T t) {
        DERIVED *self = static_cast<DERIVED*>(this);
        self->insert(t.turnUpsideDown());
    }
    void insertBackToFront(T t) // etc.
    void insert(T t, Orientation o) // this one is tricksy because it's an
                                    // overload, so requires the 'using' declaration
};

template<typename T>
class MyCollection : public OtherInsertFunctions<MyCollection,T> {
public:
    // using declaration, to prevent hiding of base class overloads
    using OtherInsertFunctions<MyCollection,T>::insert;
    void insert(T t);
    // and the rest of the class goes here
};

とにかく、そのようなもの。しかし、他の人が言ったように、C++ プログラマーは解放関数に反対することを「想定」していません。特定のクラス。すべてを一貫してメソッドにすることは、より Java 的です。

于 2008-12-02T14:50:57.353 に答える
0

はい、それらはグローバルまたは名前空間スコープのいずれかである必要があります。非メンバーの非フレンド関数は、ドット表記を使用する C# でよりきれいに見えます (これらは拡張メソッドと呼ばれます)。

于 2008-12-01T22:34:04.517 に答える