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

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

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

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

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

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

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

ねえNFL、ジョーバロウとカイラーマレーは女性の権利をサポートするために少しの助けを使うことができます

ねえNFL、ジョーバロウとカイラーマレーは女性の権利をサポートするために少しの助けを使うことができます

ジョー・バロウロー対ウェイド事件の転覆に対応するNFLは、言葉を言わないことで、腹立たしいが予測可能なPRの結果でした。

別の日、別のヒンジのないLIV記者会見

別の日、別のヒンジのないLIV記者会見

(lから)パット・ペレス、ブルックス・ケプカ、パトリック・リードサウジアラビアのLIVゴルフリーグのさらに別の信じられないほどの記者会見で、スポーツのファンはブルックス・ケプカからでたらめな吐き気と質問回避の驚異的なマスタークラスを受けました。パトリックリード、ブライソンデシャンボー、パットペレス、最近のPGAツアーの脱北者。

ミズ・マーベルの家族の帰郷は悪役よりも激しく打撃を与える

ミズ・マーベルの家族の帰郷は悪役よりも激しく打撃を与える

レッドダガーとマーベルさんがチームを組んでいます。

6億7500万ドルのビットコインローンのデフォルト後にすべての資産を清算するように命じられたスリーアローズキャピタル

6億7500万ドルのビットコインローンのデフォルト後にすべての資産を清算するように命じられたスリーアローズキャピタル

暗号業界最大の沈没船の1つであるスリーアローズキャピタルは、ついにその悲惨さから解放されています。火曜日に、不良債権ヘッジファンドは、債権者からの返済を要求する訴訟の高まりに応えて、バージンアイランド裁判所によって清算を命じられました彼らが3ACに行ったローン。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

Suffragettes Indicam #3: Junho

Suffragettes Indicam #3: Junho

Mais um mês se findando — e metade do ano de 2022 já passou. Sabe o que isso significa? Não, não é hora de verificar se você está cumprindo com suas resoluções de Ano Novo.

多元宇宙—Junø

多元宇宙—Junø

チェーン間アカウントがJunoに登場します。異なるブロックチェーン間でスマートコントラクトの構成可能性と真の相互運用性を提供します。

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

1.ドラマを見た後、起業する考えはありますか?あなたのビジネスはボトルネックに遭遇しましたか?方向性がなくてわからない場合は、ドラマを追いかけて行くことを心からお勧めします。(?)ブラフではなく、最も完璧なビジネス例を隠すドラマがあります。2.ブレイキング・バッドとその弁護士ドラマ「ブレイキング・バッド」を見た友人たちは、演劇の中で、穏やかな表情で、弁護士のソウル・グッドマンに深く感銘を受けなければなりません。口を開けて、感覚の弱い傭兵の性格を持っています。道徳の面で、サル・グッドマンは無意識のうちに劇に欠かせない役割を果たし、彼自身のシリーズ「絶望的な弁護士」(ベター・コール・ソール)を生み出しました。ウェントウのテキストとビデオは、劇中のソウル・グッドマンのテレビコマーシャルです。製品(サービス)、競争戦略、市場ポジショニング、ブランド名、ターゲット顧客グループ、コミュニケーション軸から広告まで、サル・グッドマンの役割のビジネス設定は、「最低」と見なすことができる超超超超超超完全です。ブランドコミュニケーションのコスト」「変化」のモデル。なぜ?私の分析をご覧ください。3.ソウル・グッドマンの「事業戦略」1.基本情報ブランド名:Saul Goodman製品:法律相談サービス対象顧客:麻薬中毒、飲酒運転、事故など。法律知識の欠如は、一般的に公立弁護士にしか余裕がなく、真面目な弁護士も「特別な法律を持つ消費者」を避けます。恐れてはいけない「​​ニーズ」。コミュニケーションの主軸:この国のすべての男性、女性、子供は有罪判決を受けるまで無実だと思います。地域:アルバカーキ市スローガン:Thrallに電話したほうがいいです!(ベター・コール・ソール)広告:2つの可能性のある犯罪状況をシミュレートします+サウルの主張+サウルのスローガン2をより適切に呼び出します。

メインネットガイド— Arbitrum Odyssey Week 2

メインネットガイド— Arbitrum Odyssey Week 2

最新のアップデートを受け取るために私たちに従ってください。ニュースレター:https://www。

Language