14

私はこの記事を読みましたが、メンバー関数へのポインターを呼び出すときは、インスタンス(1つへのポインターまたはスタック参照のいずれか)が必要であり、次のように呼び出すことができます。

(instance.*mem_func_ptr)(..)
or
(instance->*mem_func_ptr)(..)

私の質問はこれに基づいています:インスタンスあるので、次のようにメンバー関数を直接呼び出してみませんか?

instance.mem_func(..) //or: instance->mem_func(..)

メンバー関数へのポインターの合理的/実用的な使用法は何ですか?

[編集]

私はX-developmentで遊んでいて、ウィジェットを実装する段階に達しました。Xイベントをクラスとウィジェットに変換するためのevent-loop-threadは、それらのイベントが到着したときに、各ウィジェット/ウィンドウのスレッドを開始する必要があります。これを適切に行うには、クラスのイベントハンドラーへの関数ポインターが必要だと思いました。

そうではありません。私が発見したのは、仮想基本クラスを使用するだけで、同じことをはるかに明確でわかりやすい方法で実行できるということでした。メンバー関数へのポインタはまったく必要ありません。上記を開発しているときに、メンバー関数へのポインターの実用性/意味について疑問が生じました。

member-function-pointerを使用するためにインスタンスへの参照が必要であるという単純な事実は、インスタンスの必要性を廃止します。

[編集-@sbiなど]

これが私のポイントを説明するためのサンプルプログラムです:(特に'Handle_THREE()'に注意してください)

#include <iostream>
#include <string>
#include <map>


//-----------------------------------------------------------------------------
class Base
{
public:
    ~Base() {}
    virtual void Handler(std::string sItem) = 0;
};

//-----------------------------------------------------------------------------
typedef void (Base::*memfunc)(std::string);

//-----------------------------------------------------------------------------
class Paper : public Base
{
public:
    Paper() {}
    ~Paper() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling paper\n"; }
};

//-----------------------------------------------------------------------------
class Wood : public Base
{
public:
    Wood() {}
    ~Wood() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling wood\n"; }
};


//-----------------------------------------------------------------------------
class Glass : public Base
{
public:
    Glass() {}
    ~Glass() {}
    virtual void Handler(std::string sItem) { std::cout << "Handling glass\n"; }
};

//-----------------------------------------------------------------------------
std::map< std::string, memfunc > handlers;
void AddHandler(std::string sItem, memfunc f) { handlers[sItem] = f; }

//-----------------------------------------------------------------------------
std::map< Base*, memfunc > available_ONE;
void AddAvailable_ONE(Base *p, memfunc f) { available_ONE[p] = f; }

//-----------------------------------------------------------------------------
std::map< std::string, Base* > available_TWO;
void AddAvailable_TWO(std::string sItem, Base *p) { available_TWO[sItem] = p; }

//-----------------------------------------------------------------------------
void Handle_ONE(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        std::map< Base*, memfunc >::iterator it;
        Base *inst = NULL;
        for (it=available_ONE.begin(); ((it != available_ONE.end()) && (inst==NULL)); it++)
        {
            if (it->second == f) inst = it->first;
        }
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_TWO(std::string sItem)
{
    memfunc f = handlers[sItem];
    if (f)
    {
        Base *inst = available_TWO[sItem];
        if (inst) (inst->*f)(sItem);
        else std::cout << "No instance of handler for: " << sItem << "\n";
    }
    else std::cout << "No handler for: " << sItem << "\n";
}

//-----------------------------------------------------------------------------
void Handle_THREE(std::string sItem)
{
    Base *inst = available_TWO[sItem];
    if (inst) inst->Handler(sItem);
    else std::cout << "No handler for: " << sItem << "\n";
}


//-----------------------------------------------------------------------------
int main()
{
    Paper p;
    Wood w;
    Glass g;


    AddHandler("Paper", (memfunc)(&Paper::Handler));
    AddHandler("Wood", (memfunc)(&Wood::Handler));
    AddHandler("Glass", (memfunc)(&Glass::Handler));

    AddAvailable_ONE(&p, (memfunc)(&Paper::Handler));
    AddAvailable_ONE(&g, (memfunc)(&Glass::Handler));

    AddAvailable_TWO("Paper", &p);
    AddAvailable_TWO("Glass", &g);

    std::cout << "\nONE: (bug due to member-function address being relative to instance address)\n";
    Handle_ONE("Paper");
    Handle_ONE("Wood");
    Handle_ONE("Glass");
    Handle_ONE("Iron");

    std::cout << "\nTWO:\n";
    Handle_TWO("Paper");
    Handle_TWO("Wood");
    Handle_TWO("Glass");
    Handle_TWO("Iron");

    std::cout << "\nTHREE:\n";
    Handle_THREE("Paper");
    Handle_THREE("Wood");
    Handle_THREE("Glass");
    Handle_THREE("Iron");
}

{編集]上記の例の直接呼び出しに関する潜在的な問題
Handler_THREE()では、メソッドの名前をハードコーディングする必要があります。メソッドに変更を適用するには、メソッドが使用されている場所で変更を強制します。メンバー関数へのポインターを使用して行われる唯一の追加の変更は、ポインターが作成される場所です。

[編集]回答から収集した実用的な使用法:Chubsdadによる

回答 から:内容: 専用の「呼び出し元」関数を使用してmem-func-ptrを呼び出します。利点:他のオブジェクトによって提供される関数を使用してコードを保護する方法:特定の関数が多くの場所で使用され、名前やパラメーターが変更された場合、割り当てられた名前を変更するだけで済みます。ポインタを指定し、「呼び出し元」関数で呼び出しを調整します。(関数をinstance.function()として使用する場合は、どこでも変更する必要があります。)


Matthew Flaschenによる回答から:内容:
クラスのローカルスペシャライゼーション
利点:コードをはるかに明確、シンプル、使いやすく、保守しやすくします
方法:複雑なロジックを使用して従来実装されていたコードを(潜在的に)大きなswitch()/ifに置き換えます-次に、スペシャライゼーションへの直接ポインタを含むステートメント。上記の「発信者」関数とかなり似ています。

4

12 に答える 12

12

関数ポインタを使用するのと同じ理由:任意のプログラムロジックを使用して、関数ポインタ変数を呼び出す前に設定できます。スイッチ、if / elseを使用して、それを関数に渡すことができます。

編集:

質問の例は、メンバー関数へのポインターの代わりに仮想関数を使用できる場合があることを示しています。プログラミングには通常複数のアプローチがあるため、これは驚くべきことではありません。

これは、仮想関数がおそらく意味をなさない場合の例です。OPのコードと同様に、これは説明を目的としたものであり、特に現実的なものではありません。パブリックテスト関数を備えたクラスを示しています。これらは、内部のプライベート関数を使用します。内部関数はセットアップ後にのみ呼び出すことができ、後でティアダウンを呼び出す必要があります。

#include <iostream>

class MemberDemo;
typedef void (MemberDemo::*MemberDemoPtr)();

class MemberDemo
{
    public:
    void test1();
    void test2();

    private:
    void test1_internal();
    void test2_internal();
    void do_with_setup_teardown(MemberDemoPtr p);
};

void MemberDemo::test1()
{
    do_with_setup_teardown(&MemberDemo::test1_internal);
}

void MemberDemo::test2()
{
    do_with_setup_teardown(&MemberDemo::test2_internal);
}

void MemberDemo::test1_internal()
{
    std::cout << "Test1" << std::endl;
}

void MemberDemo::test2_internal()
{
    std::cout << "Test2" << std::endl;
}

void MemberDemo::do_with_setup_teardown(MemberDemoPtr mem_ptr)
{
    std::cout << "Setup" << std::endl;
    (this->*mem_ptr)();
    std::cout << "Teardown" << std::endl;
}

int main()
{
    MemberDemo m;
    m.test1();
    m.test2();
}
于 2010-10-18T07:53:46.173 に答える
8

私の質問はこれに基づいています。インスタンスがあるので、メンバー関数を直接呼び出さないのはなぜですか[?]

前もって: 15 年以上の C++ プログラミングで、メンバー ポインターをおそらく 2 回か 3 回使用しました。仮想関数が存在するため、それをあまり使用することはありません。

オブジェクト (または多くのオブジェクト) で特定のメンバー関数を呼び出したい場合に使用し、呼び出すオブジェクトを見つける前に、呼び出すメンバー関数を決定する必要があります。これをやりたい人の例を次に示します

于 2010-10-18T08:03:25.187 に答える
4

メンバー関数は、多くの関数ポインターと同様に、コールバックとして機能します。メソッドを呼び出す抽象クラスを作成することで、それらがなくても管理できますが、これは多くの余分な作業になる可能性があります。

一般的な用途の 1 つはアルゴリズムです。std::for_each では、コレクションの各メンバーのクラスのメンバー関数を呼び出したい場合があります。また、コレクションの各メンバーで独自のクラスのメンバー関数を呼び出したい場合もあります。後者は達成するために boost::bind が必要です。前者は STL mem_fun ファミリーのクラスで実行できます (この場合もboost::bindが必要です)。特定のルックアップまたはソートアルゴリズムで述語としてメンバー関数を使用することもできます。(これにより、operator() をオーバーロードしてクラスのメンバーを呼び出すカスタム クラスを作成する必要がなくなります。boost::bind に直接渡すだけです)。

前述したように、もう 1 つの用途はコールバックであり、多くの場合、イベント ドリブン コードで使用されます。操作が完了したら、完了を処理するために呼び出されるクラスのメソッドが必要です。これは多くの場合、boost::bind ファンクターにラップできます。この場合、これらのオブジェクトの有効期間とスレッドセーフを正しく管理するように細心の注意を払う必要があります (特に、何か問題が発生した場合にデバッグが非常に困難になる可能性があるため)。それでも、大量の「ラッパー」コードを書く手間を省くことができます。

于 2010-10-18T09:31:32.567 に答える
4

メンバー関数へのポインターの真の有用性は、boost::bind(). これにより、後で特定のオブジェクト インスタンスにバインドできるオブジェクトとして関数呼び出しをラップし、コピー可能なオブジェクトとして渡すことができます。これは、遅延コールバック、デリゲート、および洗練された述語操作を可能にする非常に強力なイディオムです。いくつかの例については、私の以前の投稿を参照してください。

https://stackoverflow.com/questions/1596139/hidden-features-and-dark-corners-of-stl/1596626#1596626

于 2010-10-18T08:28:00.050 に答える
3

多くの実用的な用途があります。私の頭に浮かぶのは次のとおりです。

以下のようなコア関数を想定します(適切に定義されたmyfooとMFN)

void dosomething(myfoo &m, MFN f){   // m could also be passed by reference to 
                                     // const
   m.*f();
}

メンバー関数へのポインターが存在する場合のこのような関数は、拡張のために開かれ、変更のために閉じられます(OCP

メンバーへのポインタをスマートに使用するSafeboolイディオムも参照してください。

于 2010-10-18T07:56:19.553 に答える
3

メンバーへのポインター関数型のポインターの要点は、特定のメソッドを参照する実行時の方法として機能することです。メソッドアクセスに「通常の」構文を使用する場合

object.method();
pointer->method();

この部分は、呼び出すメソッドのコンパイル時methodの固定仕様です。プログラムにハードコードされています。それは決して変わることはできません。ただし、メンバーへのポインター関数型のポインターを使用すると、その固定部分を、メソッドの実行時の指定で変更可能な変数に置き換えることができます。

これをよりよく説明するために、次の簡単な例えを考えてみましょう。配列があるとしましょう

int a[100];

固定のコンパイル時インデックスを使用してその要素にアクセスできます

a[5]; a[8]; a[23];

この場合、特定のインデックスはプログラムにハードコードされています。ただし、ランタイム インデックス (整数変数) を使用して配列の要素にアクセスすることもできます。i

a[i];

の値はi固定されておらず、実行時に変更される可能性があるため、実行時に配列のさまざまな要素を選択できます。これは、メンバーへのポインター関数型のポインターでできることと非常によく似ています。

あなたが求めている質問 (「インスタンスがあるので、メンバー関数を直接呼び出さない理由」) は、この配列コンテキストに変換できます。あなたは基本的に次のように尋ねています:「やa[i]のようなコンパイル時の直接定数アクセスがあるのに、なぜ変数インデックス アクセスが必要なのですか?」この質問に対する答えを知り、特定の配列要素を実行時に選択することの価値を理解していただければ幸いです。a[1]a[3]

メンバーへのポインター関数型のポインターにも同じことが当てはまります。これらを使用すると、特定のクラス メソッドの実行時選択を実行できます。

于 2010-10-19T02:20:57.360 に答える
3

メンバー関数へのポインターの最適な使用法は、依存関係を壊すことです。

メンバー関数へのポインターが必要な良い例は、サブスクライバー/パブリッシャー パターンです。

http://en.wikipedia.org/wiki/Publish/subscribe

于 2010-10-18T08:51:14.023 に答える
3

In my opinion, member function pointers do are not terribly useful to the average programmer in their raw form. OTOH, constructs like ::std::tr1::function that wrap member function pointers together with a pointer to the object they're supposed to operate on are extremely useful.

Of course ::std::tr1::function is very complex. So I will give you a simple example that you wouldn't actually use in practice if you had ::std::tr1::function available:

// Button.hpp
#include <memory>

class Button {
 public:
   Button(/* stuff */) : hdlr_(0), myhandler_(false) { }
   ~Button() {
      // stuff
      if (myhandler_) {
         delete hdlr_;
      }
   }
   class PressedHandler {
    public:
      virtual ~PressedHandler() = 0;

      virtual void buttonPushed(Button *button) = 0;
   };

   // ... lots of stuff

   // This stores a pointer to the handler, but will not manage the
   // storage.  You are responsible for making sure the handler stays
   // around as long as the Button object.
   void setHandler(const PressedHandler &hdlr) {
      hdlr_ = &hdlr;
      myhandler_ = false;
   }

   // This stores a pointer to an object that Button does not manage.  You
   // are responsible for making sure this object stays around until Button
   // goes away.
   template <class T>
   inline void setHandlerFunc(T &dest, void (T::*pushed)(Button *));

 private:
   const PressedHandler *hdlr_;
   bool myhandler_;

   template <class T>
   class PressedHandlerT : public Button::PressedHandler {
    public:
      typedef void (T::*hdlrfuncptr_t)(Button *);

      PressedHandlerT(T *ob, hdlrfuncptr_t hdlr) : ob_(ob), func_(hdlr) { }
      virtual ~PressedHandlerT() {}

      virtual void buttonPushed(Button *button) { (ob_->*func_)(button); }

    private:
      T * const ob_;
      const hdlrfuncptr_t func_;
   };
};

template <class T>
inline void Button::setHandlerFunc(T &dest, void (T::*pushed)(Button *))
{
   PressedHandler *newhandler = new PressedHandlerT<T>(&dest, pushed);
   if (myhandler_) {
      delete hdlr_;
   }
   hdlr_ = newhandler;
   myhandler_ = true;
}

// UseButton.cpp
#include "Button.hpp"
#include <memory>

class NoiseMaker {
 public:
   NoiseMaker();
   void squee(Button *b);
   void hiss(Button *b);
   void boo(Button *b);

 private:
   typedef ::std::auto_ptr<Button> buttonptr_t;
   const buttonptr_t squeebutton_, hissbutton_, boobutton_;
};


NoiseMaker::NoiseMaker()
     : squeebutton_(new Button), hissbutton_(new Button), boobutton_(new Button)
{
   squeebutton_->setHandlerFunc(*this, &NoiseMaker::squee);
   hissbutton_->setHandlerFunc(*this, &NoiseMaker::hiss);
   boobutton_->setHandlerFunc(*this, &NoiseMaker::boo);
}

Assuming Button is in a library and not alterable by you, I would enjoy seeing you implement that cleanly using a virtual base class without resorting to a switch or if else if construct somewhere.

于 2010-10-18T09:06:20.303 に答える
2

ユースケースは、同じシグニチャを持つ複数のメンバーメソッドがあり、特定の状況で呼び出す必要があるロジックを構築する場合です。これは、ステートマシンアルゴリズムを実装するのに役立ちます。

毎日使うものではありません...

于 2010-10-18T07:55:54.687 に答える
2

渡されたパラメーターに応じて、いくつかの異なる関数の1つを呼び出すことができる関数があると想像してみてください。

巨大なif/else ifステートメント
を使用できます。switchステートメントを使用
できます。または、関数ポインターのテーブル(ジャンプテーブル)を使用できます。

さまざまなオプションがある場合は、ジャンプテーブルを使用すると、コードをよりクリーンに配置できます...

しかし、それは個人的な好みにかかっています。Switchステートメントとジャンプテーブルは、とにかくほぼ同じコンパイル済みコードに対応します:)

于 2010-10-18T07:56:51.357 に答える
2

メンバー ポインター + テンプレート = 純粋な勝利。

たとえば、コンパイル時にクラスに特定のメンバー関数が含まれているかどうかを確認する方法

また

template<typename TContainer,
         typename TProperty,
         typename TElement = decltype(*Container().begin())>
TProperty grand_total(TContainer& items, TProperty (TElement::*property)() const)
{
   TProperty accum = 0;
   for( auto it = items.begin(), end = items.end(); it != end; ++it) {
       accum += (it->*property)();
   }
   return accum;
}

auto ship_count = grand_total(invoice->lineItems, &LineItem::get_quantity);
auto sub_total = grand_total(invoice->lineItems, &LineItem::get_extended_total);
auto sales_tax = grand_total(invoice->lineItems, &LineItem::calculate_tax);
于 2010-10-19T02:38:24.620 に答える
1

それを呼び出すには、インスタンスへの参照が必要ですが、関数を直接呼び出すことができ、それへのポインターは必要ありません。

これは完全に的外れです。ここには 2 つの独立した懸念事項があります。

  • 後の時点で取るべき行動
  • そのアクションを実行するオブジェクト

インスタンスへの参照を持つことで、2 番目の要件が満たされます。メンバ関数へのポインタは最初のものに対処します。これらは、プログラムの実行のある時点で、実行の後の段階で、おそらくプログラムの別の部分によってどのアクションを実行する必要があるかを記録するための非常に直接的な方法です。

人にキスしたり、くすぐったりできるサルがいるとします。午後 6 時に、プログラムは猿を解き放ち、猿が誰を訪ねるべきかを認識しますが、午後 3 時ごろ、ユーザーはどのアクションを実行する必要があるかを入力します。

初心者のアプローチ

したがって、午後 3 時に変数「enum Action { Kiss, Tickle } action;」を設定し、午後 6 時に「if (action == Kiss) monkey->kiss(person); else monkey-> tickle」のようなことを行うことができます。 (人)"。

問題

しかし、それは余分なレベルのエンコーディングを導入しています (これをサポートするために Action タイプが導入されました - 組み込みタイプを使用できますが、エラーが発生しやすく、本質的に意味がありません)。次に、午後 3 時に実行する必要があるアクションを決定した後、午後 6 時にそのエンコードされた値を冗長に参照して、実行するアクションを決定する必要があります。これには、別の if/else またはエンコードされた値に切り替える必要があります。それはすべて不器用で、冗長で、遅く、エラーが発生しやすいものです。

メンバー関数ポインター

A better way is to use a more specialised varibale - a member function pointer - that directly records which action to perform at 6pm. That's what a member function pointer is. It's a kiss-or-tickle selector that's set earlier, creating a "state" for the monkey - is it a tickler or a kisser - which can be used later. The later code just invokes whatever function's been set without having to think about the possibilities or have any if/else-if or switch statements.

To invoke it, you need a reference to an instance, but then you can call the func direct & don't need a pointer to it.

Back to this. So, this is good if you make the decision about which action to take at compile time (i.e. a point X in your program, it'll definitely be a tickle). Function pointers are for when you're not sure, and want to decouple the setting of actions from the invocation of those actions.

于 2010-10-18T09:05:18.200 に答える