2010-12-09 88 views
7

大部分关于成员指针的讨论都关注成员所属类型允许的转换。我的问题是关于成员类型的转换。指向数据成员与非成员指针的隐式转换

struct Base{}; 
struct Derived : public Base{}; 
struct Foo{ Derived m_Derived; }; 

鉴于这些声明,下面的代码会产生一个错误(2008 MSVC):来自*基地*通常允许

// error C2440: 'initializing' : cannot convert from 'Derived Foo::* ' to 'Base Foo::* ' 
Base Foo::*p = &Foo::m_Derived; 

转换 - 这里为什么不同?

+1

“通常允许从基础*转换为派生*” 我认为你有这个落后。 – 2010-12-09 22:33:03

+0

好的。固定。 – Chris 2010-12-09 22:38:59

+0

见http://stackoverflow.com/questions/4295117/pointer-to-member-conversion – icecrime 2010-12-09 22:57:59

回答

0

有趣的问题。数据指针很少使用,我对规则很不熟悉。

但是,我打算把钱归因于多重继承。如果Base和Derived使用虚拟继承,编译器无法知道任何给定Derived中Base的偏移量,从而无法编译时偏移,并且将其合法化为非虚拟遗产。

2

你有反差。

返回类型隐式转换为基本类型(逆变)。但是参数隐式转换为派生类型(协方差),并且指向成员的类类型可以作为参数。要看到这一点,让我们应用Liskov可替代性原则:

Base*的合同是:“我会给你一个基地”(当你对我使用*运营商时)。 Derived*合同是“我会给你一个派生的,这也是一个基地”。

显然,Derived*可以用来代替Base*。因此存在从Derived*Base*的隐式转换。

但是考虑指向成员的指针。

int Base::*的合同是:“给我一个基地,我会还给你一个int”(A派生是基础,所以这些都是OK太) 的int Derived::*的合同是:“给我一个推导我会给你回一个int”(但不是任何旧Base会做,它必须是一个Derived

假设你有一个Base这不是一个Derived。当解引用int Base::*时它会很好地工作,但不能与int Derived*一起使用)。

但是,如果您有 Derived,则可以使用它取消引用 int Base::*int Derived::*。因此,有从 int Base::*int Derived::*

哎呀隐式转换,我没有你所说的话,并分析了该成员所属的类型。

虽然LSP仍然有效。我同意转换应该是合法的,至少根据类型安全。合同是“给我一个Foo,我会给你一个Derived”,这显然你可以用它来从FooBase,通过组成一个隐式转换。所以它很安全。 DeadMG可能在正确的轨道上指出了与基本子对象的关系位置的潜在复杂性,特别是在虚拟继承中。但是指针成员在解除引用运算符的LHS中处理这些问题,所以他们也可以得出结果。

最终答案可能只是标准并不要求转换是合法的。

1

@Ben Voigt:你为什么挑出你的正确答案?

安全指针转换是反变换的,这意味着您可以安全地向上转发,但不能向下转发。

成员转换的安全指针是同变异的,这意味着您可以安全地下注,但不能上传。

原因很容易看到,当你想到它。假设你有一个指向Base的成员的指针,这是Base的偏移量。那么如果完整的对象是派生的,那么同一个成员到派生的偏移量是多少?通常它的偏移量完全相同:当然,如果指向Base和Derived的指针是相同地址的话。如果你有多重继承和虚拟基地,事情会变得更复杂一点:)

事实上,OP的示例代码就是为什么上演不安全的最佳例子:Derived类成员的偏移量可以应用于任何Base,并且指出基本子对象的末尾,如果它实际上是Derived而不是Derived2,那么这只是确定的。