为什么C++标准允许声明指向不存在类型的类成员的指针?
我想知道为什么该语言允许声明指向成员函数/数据的指针,尽管该成员类型不存在,而且我们知道在编译时,必须知道静态类型,并且在使用之前必须知道类型必须是完整类型除了在如此受限制的情况下使用不完整的类型。
这是我的例子:
struct Foo{
int bar(bool, bool){std::cout << "Foo::barn"; 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
}
- 正如你所看到的一切都OK,直到指针的声明
pMemFn,该指针是指向类的成员函数Foo即const与只有一个参数作为char并返回std::string。但是类中没有这样的版本,Foo正如我们所知,编译器确实知道类的所有成员,那么为什么它允许呢?我知道这个指针还没有被取消引用,因此在这样的声明中没有那个类的对象,编译器只有在使用那种类型的对象取消引用它时才会抱怨,class Foo但为什么编译器允许这样的声明?
我认为编译器首先拒绝这样的声明会更合适,就像它对指向基类的指针的静态类型所做的那样。你怎么认为?允许这样做背后有什么哲学吗?谢谢!
回答
首先,您可以X在X定义类之前声明指向类成员的指针(即,只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. 但是您必须谨慎行事,因为使用 这样的指针需要知道它所表示的成员实际上存在于所指向的对象中。
无论如何,虽然(再次)它不是很常见,但关键是编译器不能拒绝 astd::string Foo::*因为,就它所知,可能有一个派生自Foo(可能在另一个翻译单元中)的类实际上包含一个std::string成员。
此外,可以reinterpret_cast在指向不相关类和不相关成员类型的成员的指针之间。规则是T X::*始终可以强制转换U Y::*为只要T和U都是对象类型或两种函数类型,除了您不能以这种方式抛弃常量性(但您可以使用额外的const_cast. 所以我可以这样做,例如:
struct Unused {};
using ObjType = int;
using FuncType = void();
我可以ObjType Unused::*用来保存指向任何类的任何对象类型的指针;我可以用来保存指向任何类的任何函数类型的指针。但是这样的指针在使用之前必须转换回它们的原始类型。FuncType Unused::*