下面是我在说什么
// some guy wrote this, used as a Policy with templates
struct MyWriter {
void write(std::vector<char> const& data) {
// ...
}
};
在一些现有的代码中,人们没有使用模板,但接口+类型擦除
class IWriter {
public:
virtual ~IWriter() {}
public:
virtual void write(std::vector<char> const& data) = 0;
};
其他人希望能与这两种方法,并写入可用
class MyOwnClass: private MyWriter, public IWriter {
// other stuff
};
个MyOwnClass实现合条件方面MyWriter。 为什么没有个MyOwnClass'继承成员函数自动实现IWriter的接口? 相反,用户必须写转发功能是做什么,但调用基类的版本,如
class MyOwnClass: private MyWriter, public IWriter {
public:
void write(std::vector<char> const& data) {
MyWriter::write(data);
}
};
我知道,在Java中,当你有一个实现的接口,并从碰巧有合适的方法一类派生的类,基类自动实现派生类的接口。
为什么不能用C ++做呢? 这似乎是很自然的事有。
Answer 1:
这是多重继承,并且有两个继承函数具有相同签名, 这两者有落实 。 这就是C ++不同于JAVA。
调用write
上,其静态类型是表达MyBigClass
哪个的遗传功能被期望将因此是不明确的。
如果write
通过基类指针仅调用,那么限定write
在派生类是没有必要的,相反在问题的权利要求。 现在,改变了问题,包括纯符,实现该功能在派生类中是必要的,使类混凝土和实例化。
MyWriter::write
不能用于虚拟调用机制MyBigClass
,因为虚拟调用机制需要接受一个隐函数IWriter* const this
,和MyWriter::write
接受一个隐含的MyWriter* const this
。 一个新的功能是必需的,它必须考虑到的地址差IWriter
子对象和MyWriter
子对象。
这在理论上是可能的编译器自动创建这个新的功能,但它是脆弱的,因为在基类的变化可能会突然导致被选择用于转发的新功能。 这是在Java中,只有单继承可能不那么脆弱(有什么功能转发到只有一个选择),但在C ++,它支持完整的多重继承,选择是明确的,我们甚至还没有在菱形继承开始或者还虚继承。
其实,这个问题(子对象地址间的差)就解决了虚拟继承。 但是,它需要额外的开销,这是没有必要的大部分时间,和C ++的指导原则是:“你不支付你不使用什么”。
Answer 2:
为什么不能用C ++做呢? 这似乎是很自然的事有。
其实,不,这是有非常自然的事情。
请注意,我的理由是基于我自己的“常识”的理解,可以从根本上有缺陷的结果。
你看,你有两种不同的方法,第一个在MyWriter,这是非虚拟和第二次是在IWriter这是虚拟的。 他们尽管“寻找”类似的完全不同。
我建议要检查这个问题 。 关于非虚方法的好处是,无论你做什么,只要他们不叫虚方法,他们的行为不会改变。 即有人从你的类派生与非虚拟方法不会通过屏蔽他们打破现有的方法。 虚拟方法被设计为覆盖。 的,价格是,有可能由不恰当地压倒一切的虚拟方法打破底层逻辑。 这是你的问题的根本。
比方说,你提议是允许的。 (自动转换为虚拟多重继承)有两种可能的解决方案:
溶液#1 MyWriter变为虚拟的。 后果:世界上所有现有的C ++代码变得容易通过错字或名称冲突打破。 不应该MyWriter方法最初将被重写,所以突然把它变成虚拟的意志(墨菲定律)打破MyWriter类的基本逻辑,当有人从个MyOwnClass派生。 这意味着,突然使MyWriter ::写虚是一个坏主意。
Soluion#2 MyWriter保持静态BUUUT它被暂时作为虚拟方法进IWriter包括在内,直到覆盖。 乍一看没有什么可担心的,但是让我们考虑一下。 IWriter实现了某种概念的你脑子里,而且是应该做的事。 MyWriter实现另一个概念。 要分配MyWriter ::写成IWriter :: write方法需要两个保证:
- 编译器必须确保MyWriter ::写做什么IWriter ::写()是应该做的。
- 编译器必须确保调用MyWriter ::从IWriter写操作不会破坏现有的MyWriter代码编程功能预计将在其他地方使用。
那么,事情是,编译器不能保证。 功能有类似的名称和参数列表,但墨菲定律,这意味着他们正在做prbably完全不同的事情。 (SINF和cosf具有相同的参数列表,例如),这是不可能的编译器将能够预测未来,并确保在发展,我们绝不会将MyWriter以这样一种方式,它会成为IWriter不兼容的改变。 所以,因为机器本身并不能作出合理的决定(无AI为),它有问你,程序员 - “你愿意做的是什么?”。 你说“重定向虚拟方法引入MyWriter ::写(),它完全不会破坏任何东西。我觉得。”
这就是为什么你必须指定要手动使用哪种方法....
Answer 3:
自动做这将是直观和令人惊讶的。 C ++不假定多个基类是彼此相关的,并且通过定义用于非静态成员嵌套名称说明符可以防止它们的成员之间的名称冲突的用户。 添加隐含的声明来MyOwnClass
其中来自签名IWriter
和MyWriter
碰撞将违反保护的名称。
然而,C ++ 11名的扩展做使我们更接近。 试想一下:
class MyOwnClass: private MyWriter, public IWriter {
public:
void write(std::vector<char> const& data) final = MyWriter::write;
};
因为它表达的是这个机制将是安全的MyWriter
不期待任何进一步的替代和方便,因为它的名字函数签名,这将是“加盟”,但仅此而已。 此外, final
会形成不良的,如果功能不含蓄virtual
,所以它会检查签名虚拟接口匹配。
一方面,大多数接口不只是发生在这样匹配。 定义此功能仅具有相同签名的工作将是安全的,但很少有用。 其定义为一个快捷方式委托函数体将是有用的,但脆弱。 因此,它可能不是真的是一个很好的功能
在另一方面,这是一个很好的设计模式提供的功能,当你不需要它是这不是虚拟的。 所以,有了这个成语,我们可以用它来编写好的代码,即使它不与现行做法配合地很好。
Answer 4:
为什么不能用C ++做呢?
我不知道你问这里。 可以用C ++来重写允许这样做? 是的,但要达到什么目的?
因为MyWriter
和IWriter
是完全不同的类,它是在C ++中的非法呼叫的成员MyWriter
通过的一个实例IWriter
。 成员指针有完全不同的类型。 而且,正如一个MyWriter*
是无法转换为一个IWriter*
,既不是一个void (MyWriter::*)(const std::vector<char>&)
转换为一个void (IWriter::*)(const std::vector<char>&)
C ++的游戏规则没有改变,只是因为有可能是结合了两个三等功。 无论类是直接相对彼此的父/子。 因此,它们被视为完全不同的类别。
记住:成员函数始终以一个附加的参数:一个this
指针,它们指向的对象。 不能调用void (MyWriter::*)(const std::vector<char>&)
上的IWriter*
。 第三类可以有本身投射到适当的基类的方法,但必须确实有这个方法 。 因此,无论你或C ++编译器必须创建它。 C的规则++需要这个。
考虑一下将要发生,使这项工作没有一个派生类的方法。
函数得到一个IWriter*
。 用户调用write
它的成员之一,采用无非就是多IWriter*
指针。 所以......究竟怎样才能使编译器生成的代码来调用MyWriter::writer
? 记住: MyWriter::writer
需要 MyWriter
实例。 而且有没有关系IWriter
和MyWriter
。
那么究竟是如何可以在编译器做本地的强制类型转换? 编译器会检查虚函数,看看是否要调用的实际功能需要IWriter
或一些其他类型。 如果需要另一种类型的,那就要指针转换成其真实类型,然后做另一次转换到由虚拟函数所需的类型。 做了这一切之后,才再能拨打电话。
所有这些开销会影响到每一个虚拟呼叫。 他们都将不得不至少检查,看看是否实际功能是打电话。 每个呼叫也将生成代码做类型转换,以防万一。
每个虚拟函数调用将在其“获取型”和条件分支。 即使是从未有可能触发该分支。 所以,你将支付的东西,不管你是否使用与否。 这不是C ++的方式。
更糟的是,虚拟呼叫的直v表的实现是不再可能。 做虚拟调度的最快方法不会是一个符合标准的实现。 在C ++委员会是不会做任何改变,将使这样的实现是不可能的。
再次,要达到什么目的? 只是,这样你就不必写一个简单的转发功能?
Answer 5:
只是要MyWriter从IWriter派生,消除个MyOwnClass的IWriter推导,并与的生活。 这应该可以解决这个问题,不应该与模板代码干扰。
文章来源: Why does C++ not let baseclasses implement a derived class' inherited interface?