我想知道为什么语言允许声明一个指向成员函数/数据的指针,尽管该成员类型不存在,而且我们知道在编译时,必须知道静态类型,并且类型必须是完整类型除了在如此受限的情况下使用不完整类型之外,还使用该类型。
这是我的例子:
struct Foo{
int bar(bool, bool){std::cout << "Foo::bar\n"; return x_;}
int x_ = 10;
void do_it()const{cout << "do_it()\n";}
void do_it(int, bool)const{cout << "do_it(int, bool)\n";};
};
int main(){
int (Foo::* pMemBar)(bool, bool) = &Foo::bar; // ok
(Foo{}.*pMemBar)(0, 0); // ok
int Foo::*pMemX = &Foo::x_;
std::cout << Foo{}.*pMemX << '\n';
std::string (Foo::* pMemFn)(char)const; // why this is allowed?
std::string Foo::* pMemDt = nullptr; // why allowed
}
pMemFn
之前一切正常,该指针是指向类 Foo
的成员函数的指针,该成员函数为 const
并采用单个参数作为char
并返回 std::string
。但是在类 Foo
中没有这样的版本,而且我们知道编译器确实知道类的所有成员,为什么它允许这样做?我知道这个指针还没有被取消引用,因此在这样的声明中没有那个类的对象,编译器只有在使用那个类型的对象取消引用时才会抱怨 class Foo
但为什么编译器允许这样的声明? 我认为编译器首先拒绝这样的声明会更合适,就像它对指向基类的指针的静态类型所做的那样。你怎么认为?允许这样做背后有什么哲学吗?谢谢!
答案 0 :(得分:5)
首先,您可以在定义类 X
之前声明指向类 X
的成员的指针(即只有 { {1}} 已被看到)。因此,在这些情况下,编译器尚无法确定不存在具有适当类型的 X
成员。
除此之外,尽管没有该类型的实际成员,但仍然需要这种指向成员的指针类型是有原因的。首先,X
可以在类层次结构中上下进行:换句话说,假设我们有
static_cast
现在,从 struct Bar : Foo {
std::string s;
};
到 T Foo::*
的隐式转换始终存在,因为这是一种安全转换:如果 T Bar::*
值表示 T Foo::*
的特定成员,则任何Foo
对象也有这样的成员。当您使用 Bar
时,可以反过来:从 static_cast
转换为 T Bar::*
。您可能想知道为什么有人会想要这样做。嗯,这不是很常见,但想法是,就像 T Foo::*
可以指向从 Foo*
派生的任何对象一样(因此提供了一个通用类型来引用这样的对象,启用运行时多态性),Foo
也可以指向从 T Foo::*
派生的任何类的 T
类型的任何成员。但是您必须谨慎行事,因为使用这样的指针需要知道它所表示的成员实际上存在于所指向的对象中。
无论如何,虽然(再次)它不是很常见,但关键是编译器不能拒绝 Foo
因为,就它所知,可能有一个派生自 std::string Foo::*
的类(可能在另一个翻译单元中)实际上包含一个 Foo
成员。
此外,可以在指向无关类和无关成员类型的成员的指针之间使用 std::string
。规则是只要 reinterpret_cast
和 T X::*
都是对象类型或函数类型,U Y::*
总是可以强制转换为 T
,除了你不能抛弃constness 这种方式(但你可以通过额外的 U
来做到这一点)。所以我可以这样做,例如:
const_cast
而且我可以使用 struct Unused {};
using ObjType = int;
using FuncType = void();
来保存指向 any 类的 any 对象类型的指针;我可以使用 ObjType Unused::*
来保存指向 any 类的 any 函数类型的指针。但是这样的指针在使用之前必须转换回它们的原始类型。