メンバー関数ポインターを関数ポインターに合法的にキャストできますか?

3
Nigel Sharp 2019-07-25 09:30.

私はいくつかのC ++コードを継承し、警告を取り除くという任務を負っています。

ここでは、メンバー関数ポインターが関数ポインターにキャストされています。メンバー関数ポインターは、内部に暗黙の「this」パラメーターが含まれているという点で、関数ポインターとは「異なる」ことを理解しています。ただし、私の前任者は、メンバー関数ポインターから、追加の最初のパラメーターが挿入された関数ポインターにキャストすることにより、この事実を明示的に利用したようです。

私の質問は次のとおりです。

A)コンパイラの警告を取り除くことはできますか?

B)このコードはどの程度機能することが保証されていますか?

この質問の目的のために、私はそれを小さなmain.cppに切り詰めました:

#define GENERIC_FUNC_TYPE   void(*)(void)
#define FUNC_TYPE       int(*)(void *)

class MyClass
{
public:
    MyClass(int a) : memberA(a) {}
    int myMemberFunc()
    {
        return memberA;
    }

private:
    int memberA;
};

int main(int argc, char*argv[])
{
    int (MyClass::* memberFunc) () = &MyClass::myMemberFunc;
    MyClass myObject(1);
    std::cout << (myObject.*memberFunc)() << std::endl;
    // All good so far

    // Now get naughty, store it away in a very basic fn ptr
    void(*myStoredFunction)(void) = (GENERIC_FUNC_TYPE)memberFunc;  // Compiler warning

    // Reinterpret the fn pointer as a pointer to fn, with an extra object parameter
    int (*myExtractedFunction)(void*) = (FUNC_TYPE)myStoredFunction;

    // Call it
    std::cout << myExtractedFunction(&myObject) << std::endl;
}

コードはg ++で1つの警告とともにコンパイルされ、意図したとおりに2つの1が出力されます。

main.cpp: In function ‘int main(int, char**)’:
main.cpp:27:53: warning: converting from ‘int (MyClass::*)()’ to ‘void (*)()’ [-Wpmf-conversions]
  void(*myStoredFunction)(void) = (GENERIC_FUNC_TYPE)memberFunc; // Compiler warning
                                                     ^

私見このコードは、コンパイラの基礎となるメカニズムについて仮定しています。あるいは、これらの仮定はすべてのC ++コンパイラに有効かもしれません-誰か助けてもらえますか?

(実際のコードでは、マップに名前で関数ポインターの束全体を格納しています。これらの関数はすべて異なるシグネチャを持っているため、すべて同じシグネチャvoid(*)(void)にキャストされます。これは類似しています。上記のmyStoredFunctionに追加されます。次に、上記のmyExtractedFunctionと同様に、呼び出し時に個々の署名にキャストされます。)

4 answers

6
Jarod42 2019-07-25 16:48.

キャストを完全に回避する関数を作成するのはどうですか?

template <typename C, void (C::*M)()>
void AsFunc(void* p)
{
    (static_cast<C*>(p)->*M)();
}

その後

void(*myStoredFunction)(void) = &AsFunc<MyClass, &MyClass::myMemberFunc>;

C ++ 17では、いくつかの特徴がtemplate <auto *M> void AsFunc(void* p)あり、void(*myStoredFunction)(void) = &AsFunc<&MyClass::myMemberFunc>;

3
Pete Becker 2019-07-25 19:39.

タイトルの質問に答えるために、いいえ、メンバー関数へのポインターを関数へのポインターに合法的にキャストすることはできません。おそらく、それはそのキャストとの行の「コンパイラ警告」が言ったことです。

不適合なコード(少し単純化されすぎています)に直面したときに診断を発行するには、適合コンパイラーが必要ですが、これはそうしました。警告を出しました。これを実行すると、コンパイラーは実装固有の処理を自由に実行できます。これは、実行したように見えます。つまり、コードをコンパイルして、期待どおりの処理を実行します。

コンパイラーは、機能する任意の方法でメンバー関数へのポインターを自由に表すことができます。非仮想関数の場合、それは関数への単なる「通常の」ポインターである可能性があります。しかし、仮想関数でそれを試してください。結果はもっと厳しいに違いない。

1
Nigel Sharp 2019-07-26 16:23.

A)コンパイラの警告を取り除くことはできますか?

はい-静的関数からの呼び出しでメンバー関数をラップします

(これは@ Jarod42のテンプレートベースの回答のローテクバリアントです)

B)このコードはどの程度機能することが保証されていますか?

そうではありません(@Pete Beckerの答えを要約します)。警告を取り除くまで。

これが私たちが行ったものの要点です。コードの中断を最小限に抑えるために、シンプルに保ちました。コードで作業できる人の数を最大化するために、高度なC ++機能を避けました。

#include <iostream>

class MyClass
{
public:
    MyClass(int a) : memberA(a) {}
    static int myMemberFuncStatic(MyClass *obj)
    {
        return obj->myMemberFunc();
    }   
    int myMemberFunc()
    {
        return memberA;
    }

private:
    int memberA;
};

typedef void(*GENERIC_FUNC_TYPE)(void);
typedef int(*FUNC_TYPE)(MyClass *);

int main(int argc, char*argv[])
{
    int (* staticFunc) (MyClass *) = &MyClass::myMemberFuncStatic;
    MyClass myObject(1);
    std::cout << staticFunc(&myObject) << std::endl;
    // All good so far

    // This is actually legal, for non-member functions (like static functions)
    GENERIC_FUNC_TYPE myStoredFunction = reinterpret_cast<GENERIC_FUNC_TYPE> (staticFunc);  // No compiler warning

    // Reinterpret the fn pointer as the static function
    int (*myExtractedFunction)(MyClass*) = (FUNC_TYPE)myStoredFunction;

    // Call it
    std::cout << myExtractedFunction(&myObject) << std::endl;
}
1
Erlkoenig 2019-07-26 15:56.

関数void*ごとに異なるいくつかの引数を渡しながら、「型指定されていない」オブジェクト()で名前で関数を呼び出す必要があるように見えるため、何らかの多重ディスパッチが必要です。考えられる解決策は次のとおりです。

#include <string>
#include <iostream>
#include <stdexcept>
#include <functional>
#include <utility>
#include <map>

template <typename Subj>
using FunctionMap = std::map<std::string, std::function<void (Subj&, const std::string&)>>;

class AbstractBaseSubject {
    public:
        virtual void invoke (const std::string& fName, const std::string& arg) = 0;
};

template <typename Class>
class BaseSubject : public AbstractBaseSubject {
    public:
        virtual void invoke (const std::string& fName, const std::string& arg) {
            const FunctionMap<Class>& m = Class::functionMap;

            auto iter = m.find (fName);
            if (iter == m.end ())
                throw std::invalid_argument ("Unknown function \"" + fName + "\"");

            iter->second (*static_cast<Class*> (this), arg);
        }
};

class Cat : public BaseSubject<Cat> {
    public:
        Cat (const std::string& name) : name(name) {}
        void meow (const std::string& arg) {
            std::cout << "Cat(" << name << "): meow (" << arg << ")\n";
        }

        static const FunctionMap<Cat> functionMap;
    private:
        std::string name;
};

const FunctionMap<Cat> Cat::functionMap = {
    { "meow", [] (Cat& cat, const std::string& arg) { cat.meow (arg);  } }
};

class Dog : public BaseSubject<Dog> {
    public:
        Dog (int age) : age(age) {}
        void bark (float arg) {
            std::cout << "Dog(" << age << "): bark (" << arg << ")\n";
        }

        static const FunctionMap<Dog> functionMap;
    private:
        int age;
};

const FunctionMap<Dog> Dog::functionMap = {
    { "bark", [] (Dog& dog, const std::string& arg) { dog.bark (std::stof (arg));  }}
};

int main () {
    Cat cat ("Mr. Snuggles");
    Dog dog (7);

    AbstractBaseSubject& abstractDog = dog;     // Just to demonstrate that the calls work from the base class.
    AbstractBaseSubject& abstractCat = cat;

    abstractCat.invoke ("meow", "Please feed me");
    abstractDog.invoke ("bark", "3.14");

    try {
        abstractCat.invoke ("bark", "3.14");
    } catch (const std::invalid_argument& ex) {
        std::cerr << ex.what () << std::endl;
    }
    try {
        abstractCat.invoke ("quack", "3.14");
    } catch (const std::invalid_argument& ex) {
        std::cerr << ex.what () << std::endl;
    }
    try {
        abstractDog.invoke ("bark", "This is not a number");
    } catch (const std::invalid_argument& ex) {
        std::cerr << ex.what () << std::endl;
    }
}

ここで、この方法で呼び出される関数を持つすべてのクラスBaseSubjectは、派生する必要があります(これはCRTPです)。これらの(ここでは、クラスCatDog、のは、「科目」、それらを呼びましょう)が異なる引数と異なる機能を持っている(barkmeow-もちろん、被写体ごとに複数の機能が可能です)。各サブジェクトには、独自mapの文字列から関数までがあります。これらの関数は関数ポインタではなく、std::function<void (SubjectType&,const std::string&)>インスタンスです。それらのそれぞれは、必要な引数を渡して、オブジェクトのそれぞれのメンバー関数を呼び出す必要があります。引数は、ある種の一般的なデータ表現から取得する必要がありstd::stringます。ここでは、単純なを選択しました。データの出所に応じて、JSONまたはXMLオブジェクトの場合があります。std::functionインスタンスは、データをデシリアライズし、引数として渡す必要があります。map作成されたstatic各対象クラス内の変数、std::functionインスタンスはラムダが取り込まれています。BaseSubjectクラスは見上げfunctionインスタンスをし、それを呼び出します。サブジェクトクラスは常にから直接派生する必要があるため、BaseSubject<Subject>型のポインタはBaseSubject<Subject>*直接かつ安全ににキャストできSubject*ます。

安全でないキャストはまったくないことに注意してください。すべて仮想関数によって処理されます。したがって、これは完全にポータブルである必要があります。mapサブジェクトクラスごとに1つ持つと入力が集中しますが、異なるクラスで同じ名前の関数を使用できます。とにかく、関数ごとに個別に何らかのデータのアンパックが必要なため、内に個別のアンパックラムダがありmapます。

関数の引数が単なる抽象データ構造である場合、つまりconst std::string&、ラムダを省略して次のようにすることができます。

const FunctionMap<Cat> Cat::functionMap = {
    { "meow", &Cat::meow }
};

これは、関数ポインタとは対照的に、明確に定義され、許可されているstd::function魔法(this最初の引数を介して渡す)によって機能します。これは、すべての関数が同じ署名を持っている場合に特に役立ちます。実際、std::functionJarod42の提案を省略してプラグインすることもできます。

PS:楽しみのために、member-function-pointerをfunction-pointerにキャストできない例を次に示します。

#include <iostream>

struct A {
    char x;
    A () : x('A') {}
    void foo () {
        std::cout << "A::foo() x=" << x << std::endl;
    }
};

struct B {
    char x;
    B () : x('B') {}
    void foo () {
        std::cout << "B::foo() x=" << x << std::endl;
    }
};

struct X : A, B {
};

int main () {
    void (B::*memPtr) () = &B::foo;
    void (*funPtr) (X*) = reinterpret_cast<void (*)(X*)> (memPtr);  // Illegal!

    X x;
    (x.*memPtr) ();
    funPtr (&x);
}

私のマシンでは、これは次のように出力します。

B::foo() x=B
B::foo() x=A

Bこのクラスは、「X = A」を印刷することができないはず!これは、this多重継承が機能する場合に備えて、メンバー関数ポインターが呼び出しの前に追加される追加のオフセットを運ぶために発生します。キャストはこのオフセットを失います。したがって、キャストされた関数ポインタを呼び出すと、はthis自動的に最初のベースオブジェクトを参照し、Bが2番目のオブジェクトを参照して、間違った値を出力します。

PPS:さらに楽しく:Jarod42の提案をプラグインすると:

template <typename C, void (C::*M)(), typename Obj>
void AsFunc (Obj* p) {
    (p->*M)();
}

int main () {
    void (*funPtr) (X*) = AsFunc<B, &B::foo, X>;

    X x;
    funPtr (&x);
}

プログラムは正しく印刷します:

B::foo() x=B

の逆アセンブルを見ると、次のようになりAsFuncます。

c90 <void AsFunc<B, &B::foo, X>(X*)>:
 c90:   48 83 c7 01             add    $0x1,%rdi
 c94:   e9 07 ff ff ff          jmpq   ba0 <B::foo()>

コンパイラは1thisポインタに追加するコードを自動的に生成しました。このコードB::foothis、のB基本クラスを指すように呼び出されXます。これはで実現するためにAsFunc(内に埋め込まに反対機能main)、私は導入Objすることができますテンプレートパラメータp引数は派生型でもX、このようなAsFunc追加を行う必要があります。

Related questions

MORE COOL STUFF

Reba McEntire は、彼女が息子の Shelby Blackstock と共有する「楽しい」クリスマスの伝統を明らかにしました:「私たちはたくさん笑います」

Reba McEntire は、彼女が息子の Shelby Blackstock と共有する「楽しい」クリスマスの伝統を明らかにしました:「私たちはたくさん笑います」

Reba McEntire が息子の Shelby Blackstock と共有しているクリスマスの伝統について学びましょう。

メーガン・マークルは、自然な髪のスタイリングをめぐってマライア・キャリーと結ばれました

メーガン・マークルは、自然な髪のスタイリングをめぐってマライア・キャリーと結ばれました

メーガン・マークルとマライア・キャリーが自然な髪の上でどのように結合したかについて、メーガンの「アーキタイプ」ポッドキャストのエピソードで学びましょう.

ハリー王子は家族との関係を修復できるという「希望を持っている」:「彼は父親と兄弟を愛している」

ハリー王子は家族との関係を修復できるという「希望を持っている」:「彼は父親と兄弟を愛している」

ハリー王子が家族、特にチャールズ王とウィリアム王子との関係について望んでいると主張したある情報源を発見してください。

ワイノナ・ジャッドは、パニックに陥った休暇の瞬間に、彼女がジャッド家の家長であることを認識しました

ワイノナ・ジャッドは、パニックに陥った休暇の瞬間に、彼女がジャッド家の家長であることを認識しました

ワイノナ・ジャッドが、母親のナオミ・ジャッドが亡くなってから初めての感謝祭のお祝いを主催しているときに、彼女が今では家長であることをどのように認識したかを学びましょう.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

米国が選挙に影響を与えるロシアのハックに関するインテリジェンスレポートを発表

米国が選挙に影響を与えるロシアのハックに関するインテリジェンスレポートを発表

写真:ゲッティイメージズ。「最近の米国の選挙に関するロシアの活動と意図の評価」に関するFBI、CIA、およびNSAからの機密解除された文書は、「ロシアのウラジーミル・プーチン大統領が米国大統領選挙に影響を与えるキャンペーンを命じた」と主張している。

La。Manは45年間で収集された500,000ペニーで現金化

La。Manは45年間で収集された500,000ペニーで現金化

ペニーは、2006年7月6日、イリノイ州グレンビューのグレンビューコイン&コレクティブルズに展示されています。合計5,000ドル以上で、News-Starは報告します。

SaartjieBaartmanと黒人女性の身体の所有権について

SaartjieBaartmanと黒人女性の身体の所有権について

19世紀のフランスの版画サラ・バートマンのラベル・ホッテントットウィキメディア・コモンズ黒人女性の体の所有権の認識は、最も醜い歴史の断片に深く織り込まれている問題です。この有毒な考えは、地球の一部に隔離されていません。

マライア・キャリーが大晦日のパフォーマンスでソーシャルメディアのメルトダウンに対応:「ShitHappens」

マライア・キャリーが大晦日のパフォーマンスでソーシャルメディアのメルトダウンに対応:「ShitHappens」

写真:APマライアキャリーは、土曜日の夜にタイムズスクエアでディッククラークスの新年のロッキンイブウィズライアンシークレスト(このショーのタイトルはサウンドチェックを取得できますか?)のパフォーマンス中に、かなり深刻なオーディオの誤動作に苦しんでいました。最悪の問題は、キャリーの「エモーション」のパフォーマンス中に発生しました。トラックがキャリーのボーカルを断続的にしか再生せず、リップシンクをオフにしました。

米国のフィギュア スケートは、チーム イベントでの最終決定の欠如に「苛立ち」、公正な裁定を求める

米国のフィギュア スケートは、チーム イベントでの最終決定の欠如に「苛立ち」、公正な裁定を求める

ロシアのフィギュアスケーター、カミラ・バリエバが関与したドーピング事件が整理されているため、チームは2022年北京冬季オリンピックで獲得したメダルを待っています。

Amazonの買い物客は、わずか10ドルのシルクの枕カバーのおかげで、「甘やかされた赤ちゃんのように」眠れると言っています

Amazonの買い物客は、わずか10ドルのシルクの枕カバーのおかげで、「甘やかされた赤ちゃんのように」眠れると言っています

何千人ものAmazonの買い物客がMulberry Silk Pillowcaseを推奨しており、現在販売中. シルクの枕カバーにはいくつかの色があり、髪を柔らかく肌を透明に保ちます。Amazonで最大46%オフになっている間にシルクの枕カバーを購入してください

パデュー大学の教授が覚醒剤を扱った疑いで逮捕され、女性に性的好意を抱かせる

パデュー大学の教授が覚醒剤を扱った疑いで逮捕され、女性に性的好意を抱かせる

ラファイエット警察署は、「不審な男性が女性に近づいた」という複数の苦情を受けて、12 月にパデュー大学の教授の捜査を開始しました。

コンセプト ドリフト: AI にとって世界の変化は速すぎる

コンセプト ドリフト: AI にとって世界の変化は速すぎる

私たちの周りの世界と同じように、言語は常に変化しています。以前の時代では、言語の変化は数年または数十年にわたって発生していましたが、現在では数日または数時間で変化する可能性があります。

SF攻撃で91歳のアジア人女性が殴られ、コンクリートに叩きつけられた

犯罪擁護派のオークランドが暴力犯罪者のロミオ・ロレンゾ・パーハムを釈放

SF攻撃で91歳のアジア人女性が殴られ、コンクリートに叩きつけられた

認知症を患っている 91 歳のアジア人女性が最近、47 番街のアウター サンセット地区でロメオ ロレンゾ パーハムに襲われました。伝えられるところによると、被害者はサンフランシスコの通りを歩いていたところ、容疑者に近づき、攻撃を受け、暴行を受けました。

ℝ

“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.

メリック・ガーランドはアメリカに失敗しましたか?

バイデン大統領の任期の半分以上です。メリック・ガーランドは何を待っていますか?

メリック・ガーランドはアメリカに失敗しましたか?

人々にチャンスを与えることは、人生で少し遅すぎると私は信じています。寛大に。

Language