我见过的关于成员指针的大多数讨论都集中在成员所属类型允许的转换上。我的问题是关于会员类型的转换。
struct Base{};
struct Derived : public Base{};
struct Foo{ Derived m_Derived; };
鉴于这些声明,以下代码会产生错误(MSVC 2008):
// error C2440: 'initializing' : cannot convert from 'Derived Foo::* ' to 'Base Foo::* '
Base Foo::*p = &Foo::m_Derived;
通常允许从Derived *转换为Base * - 为什么差异在这里?
答案 0 :(得分:2)
你有向后的方差。
返回类型隐式转换为基类型(逆变)。但是参数隐式转换为派生类型(协方差),并且指向成员的指针中的类类型充当参数。为了看到这一点,让我们应用Liskov Substitutability Principle:
Base*
的合同是:“我会给你一个基地”(当你在我身上使用*
运算符时)。
Derived*
的合同是“我会给你一个Derived,这也是一个基础”。
显然,可以使用Derived*
代替Base*
。因此,存在从Derived*
到Base*
的隐式转换。
但请考虑指向成员的合约。
int Base::*
的合同是:“给我一个基地,我会给你一个int”(A Derived是一个基础,所以那些也可以)
int Derived::*
的合同是:“给我一个Derived,我会给你一个int”(但不是任何旧的Base
会做的,它必须是Derived
)
想象一下,您的Base
不是Derived
。取消引用int Base::*
时它会很好用,但不能与int Derived*
一起使用。
但是,如果您有Derived
,则可以使用它取消引用int Base::*
和int Derived::*
。因此,存在从int Base::*
到int Derived::*
的隐式转换
击>
唉,我做了你说的话并分析了这个成员所属的类型。
LSP仍然有效。我同意转换应该是合法的,至少根据类型安全。合同是“给我一个Foo
,我会给你一个Derived
”,显然你可以用它来Foo
到Base
转换。所以这很安全。 DeadMG可能在正确的轨道上指出了基础子对象的关系位置的潜在复杂性,特别是在虚拟继承中。但指向成员的指针在解除引用运算符的LHS上处理这些问题,因此它们也可以用于结果。
最终答案可能只是标准并不要求转换是合法的。
答案 1 :(得分:1)
@Ben Voigt:你为什么要找出正确答案?
安全指针转换是反式变体,这意味着,您可以安全地进行转发,但不能保持向下转换。
指向成员转换的安全指针是共变的,这意味着您可以安全地转发,但不能向上转发。
当你想到它时,很容易看出原因。假设你有一个指向Base成员的指针,那是Base的偏移量。那么如果完整对象是Derived,那么同一个成员对Derived的偏移量是多少?通常它的偏移完全相同:当然,如果指向Base和Derived的指针是相同的地址。如果你有多个继承和虚拟基础,事情会变得有点棘手:)
事实上,OP的示例代码是向上转换不安全的完美示例:Derived类成员的偏移量可以应用于任何Base,并指向基础子对象的末尾,这只是好的,如果它实际上是Derived而不是说Derived2。
答案 2 :(得分:0)
有趣的问题。数据指针很少使用,我对规则很不熟悉。
然而,由于多重继承,我要投入资金。如果Base和Derived使用虚拟继承,则编译器无法在编译时知道任何给定Derived中Base的偏移量,从而无法进行编译时偏移,并且将非虚拟化合法化的工作量太多了。继承。