C++の優れた点は、メンバーへのポインター型の変数を作成できることです。最も一般的な使用例は、メソッドへのポインタを取得することです:
struct foo
{
int x() { return 5; }
};
int (foo::*ptr)() = &foo::x;
foo myFoo;
cout << (myFoo.*ptr)() << '\n'; // prints "5"
しかし、いじくり回して、メンバー変数をポイントすることもできることに気づきました。
struct foo
{
int y;
};
int foo::*ptr = &foo::y;
foo myFoo;
myFoo.*ptr = 5;
cout << myFoo.y << '\n'; // prints "5"
これはかなりradです。それは私をさらなる実験に導きました:もしあなたが構造のサブメンバーへのポインターを得ることができたらどうでしょうか?
struct foo
{
int y;
};
struct bar
{
foo aFoo;
};
int bar::*foo::*ptr;
これは 実際にコンパイルされます です。
しかし、私はそれに何か有用なものを割り当てる方法がわかりません。次の作品はどれも:
int bar::*foo::*ptr = &bar::foo::y; // no member named "foo" in "bar"
int bar::*foo::*ptr = &bar::aFoo::y; // no member named "aFoo" in "bar" (??)
int bar::*foo::*ptr = &foo::y; // can't init 'int bar::*foo::*' with 'int foo::*'
さらに、これが生成するエラーによると、このタイプは正確には私が考えているものではないようです:
int bar::*foo::*ptr = nullptr;
bar myBar;
myBar.*ptr = 4; // pointer to member type ‘int bar::*’ incompatible
// with object type ‘bar’
この概念は私を避けているようです。明らかに、それが単に私が期待するものとはまったく異なる方法で解析されることを除外することはできません。
誰かが実際にint bar::*foo::*
が何であるか説明してくれませんか?なぜgccはbar
のメンバーへのポインターがbar
オブジェクトと互換性がないと通知するのですか? int bar::*foo::*
をどのように使用し、有効なものをどのように構築しますか?
このような怪物を初期化する「有効な」方法は次のとおりです。
struct bar;
struct foo
{
int y;
int bar::* whatever;
};
struct bar
{
foo aFoo;
};
int bar::* foo::* ptr = &foo::whatever;
ご覧のとおり、ptr
はfoo
(foo::*
、右から左に読む)、そのメンバー自体がbar
(bar::*
)、そのメンバーはintです。
Int bar :: * foo :: *をどのように使用しますか
あなたはそうしないでしょう、うまくいけば!しかし、あなたが迫害されているなら、これを試してください!
struct bar
{
foo aFoo;
int really;
};
int bar::* foo::* ptr = &foo::whatever;
foo fleh;
fleh.whatever = &bar::really;
bar blah;
blah.*(fleh.*ptr) = 42;
std::cout << blah.really << std::endl;
それは、データメンバーへのポインターであるデータメンバーへのポインターです(int
のbar
メンバー)。
それが実際に何に役立つのか私に尋ねないでください-私の頭は少し回転しています:)
編集:これが実際の例です:
#include <iostream>
struct bar {
int i;
};
struct foo {
int bar::* p;
};
int main()
{
bar b;
b.i = 42;
foo f;
f.p = &bar::i;
int bar::*foo::*ptr = &foo::p;
std::cout << (b.*(f.*ptr));
}
もちろん、出力は42です。
さらに楽しくなる可能性があります-メンバー関数へのポインターを返すメンバー関数へのポインターを次に示します。
#include <iostream>
struct bar {
int f_bar(int i) { return i; };
};
struct foo {
int(bar::*f_foo())(int)
{
return &bar::f_bar;
}
};
int main()
{
int(bar::*((foo::*ptr)()))(int) = &foo::f_foo;
bar b;
foo f;
std::cout << (b.*((f.*ptr)()))(42);
}
宣言int bar::*foo::*ptr;
を解析してみましょう。
§8.3.3[dcl.mptr]/p1:
宣言
T D
では、D
の形式はnested-name-specifier * attribute-specifier-seq_opt cv-qualifier-seq_opt D1
ネストされた名前指定子はクラスを示し、宣言
T D1
内の識別子のタイプは「derived-declarator-type-listT
」であり、次に識別子のタイプofD
is“ derived-declarator-type-listcv-qualifier-seq pointer to member of the class nested-name-specifier of typeT
」。
ステップ1:これは、T
= int、nested-name-specifier = bar::
、およびD1 = foo::* ptr
である上記のフォームの宣言です。まず、宣言T D1
、またはint foo::* ptr
を確認します。
ステップ2:同じルールを再度適用します。 int foo::* ptr
は、T
= int、nested-name-specifier = foo::
、およびD1
= ptr
である上記のフォームの宣言です。明らかにint ptr
の識別子の型は "int
"であるため、宣言int foo::* ptr
の識別子ptr
は「型foo
のクラスint
のメンバーへのポインター」です。
ステップ3.元の宣言に戻ります。 T D1
(int foo::* ptr
)の識別子のタイプは、ステップ2で「タイプfoo
のクラスint
のメンバーへのポインター」なので、派生した宣言子タイプリスト 「型のクラスfoo
のメンバーへのポインター」です。置換は、この宣言がptr
を「型foo
のクラスbar
のメンバーへのポインター型のクラスint
のメンバーへのポインター」として宣言することを示しています。
うまくいけば、あなたはそのような怪物を使う必要は決してないでしょう。
誰かが不思議に思っている場合、複数のレイヤーを深くネストするメンバーへのポインターを作成することはできません。この理由は、すべてのメンバーへのポインターが実際には一見したものよりもはるかに複雑であるためです。それらには、単にその特定のメンバーの特定のオフセットが含まれているわけではありません。
仮想継承などのため、単純なオフセットの使用は機能しません。基本的に、単一のタイプ内であっても、特定のフィールドのオフセットがインスタンス間で異なるため、実行時にメンバーへのポインター解決を実行する必要があります。これは主に、標準が非PODタイプの内部レイアウトがどのように機能するかを指定していないため、静的に機能させる方法がないためです。
これが事実である場合、2レベルの深い解決を行うことは、通常のメンバーへのポインターでは実行できませんが、1深めのメンバーへのポインターの2倍の情報を含むポインターを生成するコンパイラーが必要になります。 。
メンバーへのポインターはそれほど一般的ではないので、複数のポインターを使用して同じ結果を得ることができる場合、実際に複数層のディープメンバーを設定できる構文を作成する必要はないと思います。
まず、「読みやすさ」を向上させるために、括弧を使用できます(コンパイルは機能します)。
struct bar;
struct foo
{
int y;
int (bar:: *whatever); // whatever is a pointer upon an int member of bar.
};
struct bar
{
foo aFoo;
};
// ptr is a pointer upon a member of foo which points upon an int member of bar.
int (bar:: *(foo:: *ptr)) = &foo::whatever;
ご了承ください
int(bar :: *何でも)
に相当
int(*何でも)
バーのメンバーシップに関する制約付き。
はどうかと言うと
int(バー:: *(foo :: * ptr))
、それは
int(*(* ptr))
fooとbarのメンバーシップに関する2つの制約があります。
それらは単なるポインタです。 barまたはfooに互換性のあるメンバーが実際にあるかどうかはチェックしません。クラスbarの前方宣言の使用が妨げられ、クラスbarは他のクラスがポインターを通じてメンバーを参照しているかどうかをチェックしないためです。さらに、不透明なクラスを参照する必要がある場合もあります(つまり、クラスバーが別のユニットで定義されています)。
有用性はどうですか?クラスラッパーを介してクラスのメンバーの値を設定/取得する方法としてのC++リフレクションのためか?
template< typename Class, typename Type >
struct ClassMember
{
using MemberPointer = Type (Class:: *);
MemberPointer member;
ClassMember(MemberPointer member) : member(member) {}
void operator()(Class & object, Type value) { object.*member = value; }
Type operator()(Class & object) { return object.*member; }
};
template< typename Class, typename Type > ClassMember< Class, Type > MakeClassMember(Type(Class:: *member))
{
return ClassMember< Class, Type >(member);
}
struct Rectangle
{
double width;
double height;
Rectangle(double width = 0., double height = 0.) : width(width), height(height) {}
};
int main(int argc, char const *argv[])
{
auto r = Rectangle(2., 1.);
auto w = MakeClassMember(&Rectangle::width);
auto h = MakeClassMember(&Rectangle::height);
w(r, 3.);
h(r, 2.);
printf("Rectangle(%f, %f)\n", w(r), h(r));
return 0;
}
確かに、この例では、ダブルメンバーポインターの特定の使用法を示していません。これをここで説明する簡単な方法や、概念的にそうするための正当な理由が見当たらないためです。