私はいくつかの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と同様に、呼び出し時に個々の署名にキャストされます。)
キャストを完全に回避する関数を作成するのはどうですか?
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>;
タイトルの質問に答えるために、いいえ、メンバー関数へのポインターを関数へのポインターに合法的にキャストすることはできません。おそらく、それはそのキャストとの行の「コンパイラ警告」が言ったことです。
不適合なコード(少し単純化されすぎています)に直面したときに診断を発行するには、適合コンパイラーが必要ですが、これはそうしました。警告を出しました。これを実行すると、コンパイラーは実装固有の処理を自由に実行できます。これは、実行したように見えます。つまり、コードをコンパイルして、期待どおりの処理を実行します。
コンパイラーは、機能する任意の方法でメンバー関数へのポインターを自由に表すことができます。非仮想関数の場合、それは関数への単なる「通常の」ポインターである可能性があります。しかし、仮想関数でそれを試してください。結果はもっと厳しいに違いない。
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;
}
関数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です)。これらの(ここでは、クラスCat
とDog
、のは、「科目」、それらを呼びましょう)が異なる引数と異なる機能を持っている(bark
とmeow
-もちろん、被写体ごとに複数の機能が可能です)。各サブジェクトには、独自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::function
Jarod42の提案を省略してプラグインすることもできます。
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()>
コンパイラは1
、this
ポインタに追加するコードを自動的に生成しました。このコードB::foo
はthis
、のB
基本クラスを指すように呼び出されX
ます。これはで実現するためにAsFunc
(内に埋め込まに反対機能main
)、私は導入Obj
することができますテンプレートパラメータp
引数は派生型でもX
、このようなAsFunc
追加を行う必要があります。
特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。
ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。
ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。
ジョー・バロウロー対ウェイド事件の転覆に対応するNFLは、言葉を言わないことで、腹立たしいが予測可能なPRの結果でした。
(lから)パット・ペレス、ブルックス・ケプカ、パトリック・リードサウジアラビアのLIVゴルフリーグのさらに別の信じられないほどの記者会見で、スポーツのファンはブルックス・ケプカからでたらめな吐き気と質問回避の驚異的なマスタークラスを受けました。パトリックリード、ブライソンデシャンボー、パットペレス、最近のPGAツアーの脱北者。
暗号業界最大の沈没船の1つであるスリーアローズキャピタルは、ついにその悲惨さから解放されています。火曜日に、不良債権ヘッジファンドは、債権者からの返済を要求する訴訟の高まりに応えて、バージンアイランド裁判所によって清算を命じられました彼らが3ACに行ったローン。
Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday
シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。
オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。
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.
1.ドラマを見た後、起業する考えはありますか?あなたのビジネスはボトルネックに遭遇しましたか?方向性がなくてわからない場合は、ドラマを追いかけて行くことを心からお勧めします。(?)ブラフではなく、最も完璧なビジネス例を隠すドラマがあります。2.ブレイキング・バッドとその弁護士ドラマ「ブレイキング・バッド」を見た友人たちは、演劇の中で、穏やかな表情で、弁護士のソウル・グッドマンに深く感銘を受けなければなりません。口を開けて、感覚の弱い傭兵の性格を持っています。道徳の面で、サル・グッドマンは無意識のうちに劇に欠かせない役割を果たし、彼自身のシリーズ「絶望的な弁護士」(ベター・コール・ソール)を生み出しました。ウェントウのテキストとビデオは、劇中のソウル・グッドマンのテレビコマーシャルです。製品(サービス)、競争戦略、市場ポジショニング、ブランド名、ターゲット顧客グループ、コミュニケーション軸から広告まで、サル・グッドマンの役割のビジネス設定は、「最低」と見なすことができる超超超超超超完全です。ブランドコミュニケーションのコスト」「変化」のモデル。なぜ?私の分析をご覧ください。3.ソウル・グッドマンの「事業戦略」1.基本情報ブランド名:Saul Goodman製品:法律相談サービス対象顧客:麻薬中毒、飲酒運転、事故など。法律知識の欠如は、一般的に公立弁護士にしか余裕がなく、真面目な弁護士も「特別な法律を持つ消費者」を避けます。恐れてはいけない「ニーズ」。コミュニケーションの主軸:この国のすべての男性、女性、子供は有罪判決を受けるまで無実だと思います。地域:アルバカーキ市スローガン:Thrallに電話したほうがいいです!(ベター・コール・ソール)広告:2つの可能性のある犯罪状況をシミュレートします+サウルの主張+サウルのスローガン2をより適切に呼び出します。