在C ++中,为什么纯 virtual
方法强制要求强制覆盖仅直接子项(用于创建对象),而不是强大的子项和等等?
struct B {
virtual void foo () = 0;
};
struct D : B {
virtual void foo () { ... };
};
struct DD : D {
// ok! ... if 'B::foo' is not overridden; it will use 'D::foo' implicitly
};
我认为没有任何大不了的功能
例如,在语言设计的角度来看,只有struct DD
有D::foo
这样的明确语句时才允许using D::foo;
使用foo
。否则必须覆盖{{1}}必修。
在C ++中有这种效果的实用方法吗?
答案 0 :(得分:7)
我找到了一种机制,至少我们会提示我们明确地宣布重写的方法 。这不是完美的方式,但至少有点接近。
假设virtual
(基数)中我们的class B
方法很少:
class B {
virtual void foo () = 0;
virtual void bar (int) = 0;
};
其中,我希望foo()
被整个层次结构覆盖,然后为了简单起见将foo()
1级提升:
class Register_foo {
template<typename T> // this matches the signature of 'foo'
Register_foo (void (T::*)()) {}
};
class Base : public virtual Register_foo { // <---- virtual inheritance
virtual void bar (int) = 0;
Base () : Register_foo(&Base::foo) {} // <--- explicitly pass the function name
};
层次结构中的每个后续子类都必须在每个构造函数 显式中注册其foo
。 e.g:
struct D : B {
D () : Register_foo(&D::foo) {}
D (const D &other) : Register_foo(&D::foo) {}
virtual void foo () {};
};
此注册机制与业务逻辑无关。但至少在任何子类的构造函数中,都会提到它foo
正在使用它
但是,孩子class
可以选择使用自己的foo
或其父级foo
进行注册,但至少明确宣布。
答案 1 :(得分:3)
你基本上要求的是要求得出最多的
class实现功能。我的问题是:为什么?关于唯一
时间我可以想象这是相关的函数,如clone()
或
another()
,返回相同类型的新实例。那就是
你真正想要强制执行的是,新实例具有相同的功能
类型;甚至那里,实际实现的功能是
无关。你可以强制执行:
class Base
{
virtual Base* doClone() const = 0;
public:
Base* clone() const
{
Base* results = doClone();
assert( typeid(*results) == typeid(*this) );
return results;
}
}
(在实践中,我从未发现人们忘记覆盖clone
是一个真正的问题,所以我从来没有像上面那样烦恼。
但是,这是一种通常有用的技术,只要您想强制执行
后置条件。)
答案 2 :(得分:1)
在您的示例中,您尚未声明D::foo
纯;这就是为什么它不需要被覆盖。如果你想要再次覆盖它,那么声明它是纯粹的。
如果您希望能够实例化D
,但强制任何进一步的派生类覆盖foo
,那么您不能。但是,您可以从D
派生另一个类,将其重新声明为纯,然后从派生的类必须再次覆盖它。
答案 3 :(得分:1)
纯虚拟意味着要实例化,必须在声明纯虚函数的类的某些后代中重写纯虚拟虚拟虚拟虚拟虚拟虚拟。这可以在被实例化的类中,或者在声明纯虚拟的基础和被实例化的基础之间的任何中间类。
但是,仍然可以使用纯虚拟导出的中间类,而不会覆盖纯虚拟虚拟。与声明纯虚拟的类一样,这些类只能用作基类;你不能创建那些类的实例,只能创建从它们派生的类,其中每个纯虚拟实现了。至于要求后代覆盖虚拟,即使中间类已经这样做,答案是否定的,C ++不提供任何至少打算这样做的东西。几乎看起来你可能能够使用多个(可能是虚拟的)继承来一起破解某些东西,所以中间类中的实现会存在但是尝试使用它会是模棱两可的,但我没有想到足够的确定如何(或者如果)它将起作用 - 即使它确实如此,它只会在尝试调用有问题的函数时发挥作用,而不仅仅是实例化一个对象。
答案 4 :(得分:1)
在C ++中有这种效果的实用方法吗?
不,并且有充分的理由。如果这是标准的一部分,想象一下大型项目的维护。一些基类或中间基类需要添加一些公共接口,一个抽象接口。现在,每个孩子和孙子都需要改变和重新编译(即使它像你建议的那样简单地使用D :: foo()添加),你可能会看到它在哪里,hells kitchen。
如果您真的想要强制执行,可以强制在子类中实现其他一些纯虚拟。这也可以使用CRTP模式完成。