问题描述
考虑以下简单程序:
class Shape
{
public:
virtual double getArea() = 0;
};
class Rectangle : public Shape
{
int width;
int height;
public:
Rectangle( int w , int h ) :width(w) , height(h) {}
double getArea() const
{
return width * height;
}
};
int main() {
Rectangle* r = new Rectangle(4,2);
}
尝试编译此问题会给我:
Trying to compile this problem gives me:
'Rectangle' : cannot instantiate abstract class
为什么协变返回类型是C ++中不允许这样做?我当然可以通过将 Rectangle :: getArea
设为非常量函数来修复该程序,但我很好奇语言设计者为何另有决定.
Why is this not allowed in C++ when covariant return types are? Of course I can fix the program by making Rectangle::getArea
to be a non-const function but I am curious as to why the language designers decided otherwise.
编辑
很多人在答案中提到签名是如何不同的.但是
Lots of people have mentioned in their answers how the signature is different. But so is
class Shape
{
public:
virtual BaseArea* getArea() = 0;
};
class Rectangle : public Shape
{
public:
virtual RectangleArea* getArea();
};
但是在C#不允许的情况下,C ++会竭尽所能.
but C++ goes out of its way to allow it, when C# doesn't.
C ++支持协变返回类型,因为如果我希望接口返回 BaseArea *
,并且实现返回 RectangleArea *
,则只要RectangleArea源自BaseArea,因为符合我的合同.
C++ supports covariant return types because if I expect an interface to return a BaseArea*
and an implemention returns a RectangleArea*
, it is ok as long as RectangleArea derives from BaseArea because my contract is met.
同一行上,难道不是提供一个非变异函数的实现只满足要求变异函数的接口的实现吗?
On the same lines, isn't an implementation that provides a non-mutating function satisfying an interface that only asks for a mutating function?
推荐答案
大多数答案都说明了为什么根据当前语言规则不允许使用该规则,而不是说为什么使用这种方式编写规则.我将尝试回答为什么规则不能像您建议的那样.
Most of the answer say why it's not allowed in terms of the current rules of the language, instead of saying why the rules were written that way. I'll try to answer why the rules couldn't have been the way you suggest.
斯特罗斯特鲁普(Stroustrup)的 Design&《 C ++的发展》一书描述了为什么放宽了覆盖规则以允许协变返回的原因,而C ++并不总是这样.因此,对您的问题的一个答案是,最初的覆盖必须与签名完全匹配,并且对兼容"返回类型进行了例外处理,这不会削弱虚函数的约定.他们可能只是因为没有人考虑或没有建议而不再放松.D& E确实提到了对放宽规则的其他可能放宽,但他说:我们认为,通过放宽允许进行此类转换的好处不会超过实现成本和使用户感到困惑的潜力."这很重要,因为我认为您的想法可能会引起用户困惑,并且实际上会引起安全问题,特别是它会削弱类型系统.
Stroustrup's Design & Evolution of C++ book describes why the overriding rules were relaxed to allow covariant returns, which weren't always allowed in C++. So one answer to your question would be that originally overrides had to be exact matches for the signature, and an exception was made for "compatible" return types that don't weaken the contract of a virtual function. It's possible they just weren't relaxed further because noone thought of it or noone suggested it. D&E does mention other possible relaxations of the overriding rules but says "We felt that the benefits from allowing such conversions through overriding would not outweigh the implementation cost and the potential for confusing users." That's relevant because I think your idea has plenty of potential for confusing users, and can actually cause safety problems, specifically it weakens the type system.
考虑:
class Square : public Rectangle
{
public:
explicit Square(int side) : Rectangle(side, side) { }
virtual double getArea() // N.B. non-const, overrides Shape::getArea
{
// class author decides this would be a sensible "sanity check"
// (I'm not suggesting this is a good implementation)
if (height != width)
height = width;
return Rectangle::getArea();
}
};
const Square s(2);
int main()
{
double (Rectangle::*area)() const = &Rectangle::getArea;
double d = (s.*area)();
}
我相信您的想法将使此代码有效,在const对象上调用了const成员函数,但实际上它是一个虚函数,因此它将调用 Square :: getArea()
non-const ,因此它尝试修改const对象,该对象可以存储在只读内存中,因此会导致段错误.
I believe that your idea would make this code valid, a const member function is invoked on a const object, but actually it is a virtual function so it calls Square::getArea()
which is non-const and so it tries to modify a const object, which could be stored in read-only memory and so would result in a segfault.
这只是一个例子,说明在 Shape
示例中允许您的过度松弛会导致未定义的行为,我敢肯定,在更实际的代码中,可能会有更大甚至更微妙的问题.
This is just one example of how allowing your overriding relaxation in the Shape
example could result in undefined behaviour, I'm sure in more realistic code there could be bigger, maybe subtler problems.
您可能会争辩说,编译器不应允许非const函数覆盖 Rectangle :: getArea
,因此应拒绝 Square :: getArea
(函数已变为const,无法返回"),但这会使层次结构非常脆弱.使用具有不同常量性的 getArea
函数添加或删除中间基类将更改 Square :: getArea()
是重写还是重载.这样的虚函数已经存在一些脆弱性,特别是协变量返回,但是根据D& E Stroustrup的观点,协变量返回很有用,因为放宽允许人们在类型系统中做一些重要的事情,而不是使用强制类型转换."我不认为允许const函数覆盖非const函数非常适合类型系统,并且不允许做任何重要的事情,也不能摆脱强制转换以允许使用新的(安全)技术
You could argue that the compiler should not allow a non-const function to override Rectangle::getArea
and so should reject Square::getArea
("once a virtual function has gone const it can't go back") but that would make hierarchies very fragile. Adding or removing intermediate base classes with getArea
functions with different constness would change whether Square::getArea()
is an override or an overload. There is already some fragility like this with virtual functions, especially covariant returns, but according to D&E Stroustrup considered covariant returns useful because "the relaxation allows people to do something important within the type system instead of using casts." I don't think allowing const functions to override non-const ones fits nicely within the type-system, and doesn't allow doing anything important, and doesn't get rid of casts to allow a new (safe) techniques to be used.
这篇关于为什么const方法不会在C ++中覆盖非const方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!