Tur*_*ght 14
tl;博士:
声明成员的类是成员函数指针将绑定到的类。->*
on aDerived
不能与Base::
成员函数指针一起使用,除非您可以访问private Base
in (例如在 的成员函数中或在声明为 的朋友的函数中)。Derived
Derived
Derived
c 风格的转换允许您转换Derived*
为Base*
这些类型的成员函数指针,即使Base
不可访问(这对于任何 c++ 风格的转换都是非法的),例如:
Base* b = (Base*)&d;
在你的例子中是合法的。
1. 为什么你得到一个Base::
成员函数指针
9.9using
声明(强调我的)
12 [注5:为了在重载决议期间形成一组候选者,派生类中使用声明命名的函数被视为派生类的直接成员。特别是,隐式对象参数被视为对派生类的引用,而不是对基类 ( [over.match.funcs] ) 的引用。这对函数的类型没有影响,并且在所有其他方面,该函数仍然是基类的一部分。——尾注]
所以using
-declaration 不会创建foo
for的版本Derived
,但编译器需要假装它是Derived::
重载决议的成员。
所以在这种情况下,相关位是这对函数的类型没有影响——也就是说,Base::
如果你获取foo
.
注意:唯一的例外是构造函数
2. 为什么不能使用->*
成员Base::
指针
7.6.4 指向成员运算符(强调我的)
3二元运算符
->*
将它的第二个操作数绑定到它的第一个操作数,它的类型应该是“指向T的成员”和可访问的基类。表达式被转换为等价形式。E1->*E2
(*(E1)).*E2
这里的问题是,在你使用foo
指针的时候Base
是不可访问的,所以调用不起作用。
3.如何使它工作
成员函数实际上需要可转换为任何派生类型,只要满足以下几个条件:
7.3.13 指针到成员的转换(强调我的)
2 类型为“指向类型为cv T的B成员的指针” 类型的纯右值,其中B是类类型,可以转换为类型为“指向类型为cv T的D成员的指针” 类型的纯右值,其中D是完整类从B派生([class.derived] ) 。如果B是 D 的不可访问( [class.aess] )、不明确( [class.member.lookup] ) 或虚拟( [class.mi] ) 基类,或D的虚拟基类的基类,需要这种转换的程序是不正确的。
[...]
鉴于这Base
不像您的示例那样模棱两可或虚拟,我们需要关注的唯一问题是可访问性部分。
还是我们?
实际上,我们可以使用标准中的一个小漏洞:
7.6.3 显式类型转换(强制转换符号)(强调我的)
4执行的转换
(4.1)一个const_cast
( [expr.const.cast] ), (4.2)一个static_cast
([expr.static.cast]), (4.3) astatic_cast
后跟 aconst_cast
, (4.4) areinterpret_cast
( [expr.reinterpret.cast] ),或 (4.5) areinterpret_cast
后跟 aconst_cast
,可以使用显式类型转换的强制转换表示法来执行。相同的语义限制和行为适用,但
(4.6)指向派生类类型对象的指针或派生类类型的左值或右值可以分别显式转换为指向明确基类类型的指针或引用; (4.7)指向派生类类型成员的指针可以显式转换为指向明确非虚基类类型成员的指针; (4.8)指向明确非虚拟基类类型对象的指针、明确非虚拟基类类型的glvalue或指向明确非虚拟基类类型成员的指针可以显式转换为指针、引用或指向派生类类型成员的指针。static_cast
在以下情况下执行转换是有效的,即使基类不可访问:[...]
因此,尽管任何 C++ 转换方法(如static_cast
/reinterpret_cast
等)都不允许从Base::*
to转换,但允许Derived::*
c 样式转换执行它,即使在基类不可访问时也是如此。
例如:
int main() {
Derived d;
auto fn = &Derived::foo;
// cast to base (only legal with c-style cast)
// Base* b = static_cast<Base*>(&d); // not legal
// Base* b = reinterpret_cast<Base*>(&d); // not legal
Base* b = (Base*)&d; // legal
(b->*fn)(12);
// cast member function pointer to derived
// (also only legal with c-style cast)
using MemFn = int (Derived::*)(int) const;
// auto fnD = static_cast<MemFn>(fn); // not legal
// auto fnD = reinterpret_cast<MemFn>(fn); // not legal
auto fnD = (MemFn)fn; // legal
(d.*fnD)(12);
// or as a one liner (provided by @KamilCuk in the ments):
// slightly hard to read, but still legal c++:
(d.*((int(decltype(d)::*)(int))&decltype(d)::foo))(12); // legal
}
螺栓示例
是有效的 C++。
因此,只需将Derived
toBase
或成员函数指针强制转换为绑定到Derived
.
标准中甚至有一个例子可以做到这一点:11.8.3 基类和基类成员的可访问性(3)
4、为什么不&Derived::foo
返回Derived::*
memfn指针?
因为标准是这样说的。我不知道他们为什么会这样决定,但我可以推测可能的原因是什么:
您可以检查哪个派生最多的类实现了给定的成员函数。&Derived::foo
如果返回Derived::*
指针,这将中断。(例如,这可以与 CRTP 一起使用,以检查给定Derived
类是否为 的给定成员提供了新定义Base
)例如:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : private Base {
public:
using Base::foo;
};
template<class T>
struct implementing_class_helper;
template<class T, class R>
struct implementing_class_helper<R T::*> {
typedef T type;
};
template<class T>
struct implementing_class : implementing_class_helper<typename std::remove_cv<T>::type> {
};
template<class T>
using implementing_class_t = implementing_class<T>;
int main() {
static_assert(std::is_same_v<
typename implementing_class<decltype(&Derived::foo)>::type,
Base
>, "Shenanigans!");
}
如果要创建存根函数,例如:
class Base {
public:
int foo(int x) const { return 2*x; }
};
class Derived : Base {
public:
// pretending using Base::foo; would result in this:
int foo(int x) { return Bar::foo(x); }
};
编译器现在会遇到问题,因为Base::foo
和Derived::foo
是不同的函数,但仍然需要比较等于Base::foo
,因为这是实际的实现。
因此,编译器需要知道所有using Base::foo;
编译单元中包含的所有类,并确保无论何时将它们::foo
与结果进行比较Base::foo
,结果都是true
. 对于一个奇怪的边缘情况,这听起来像是很多实现工作。
更多推荐
指针,函数,成员
发布评论