为什么const方法不能覆盖C ++中的非const方法?

时间:2012-07-21 19:52:27

标签: c++

考虑这个简单的程序

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);
}

试图编译这个问题给了我

 'Rectangle' : cannot instantiate abstract class

为什么在协变返回类型时C ++中不允许这样做? 当然我可以通过使Rectangle::getArea成为非常量函数来修复程序,但我很好奇语言设计师为什么会这样做。

修改

很多人在他们的回答中提到签名是如何不同的。但同样如此

class Shape
{
public:
    virtual BaseArea* getArea() = 0;

};

class Rectangle : public Shape
{
public:
    virtual RectangleArea* getArea();
};

但是当C#没有时,C ++就会离开它们。

C ++支持协变返回类型,因为如果我希望接口返回一个BaseArea *和一个返回RectangleArea *的实现,那么如果RectangleArea派生自BaseArea则可以,因为我的合同已得到满足。

在同一行中,并不是一个提供非变异函数的实现,它满足只要求变异函数的接口。

6 个答案:

答案 0 :(得分:9)

因为您无法覆盖

virtual double getArea() = 0;

这与

不一样
virtual double getArea() const = 0;

这不是同一个功能;两者都可以在单个类定义中共存。

答案 1 :(得分:7)

在这种情况下会发生什么:

struct base
{
    virtual void foo(); // May implement copy-on write
    virtual void foo() const;
};

struct derived : base
{
    // Only specialize the const version, the non const
    // default suits me well. How would I specify that I don't
    // want the non-const version to be overriden ?
    void foo() const;
};

答案 2 :(得分:5)

这是因为const方法的签名与非const方法不同。所以编译器正在寻找一种未实现的方法。据推测,非const版本可以执行与const版本完全不同的操作。但是,如果这是您想要的语义,您可以轻松地提供它:

class Shape
{
public:
    virtual double getArea() {
        return static_cast<const Shape *>(this)->getArea();
    }
    virtual double getArea() const = 0;
};

在编辑中,您提供了协变返回类型的示例:

  

C ++支持协变返回类型,因为如果我希望接口返回一个BaseArea *和一个返回RectangleArea *的实现,那么如果RectangleArea派生自BaseArea则可以,因为我的合同已得到满足。

方法或函数的返回值从未成为其签名的一部分。方法的const - ness与返回值无关。它影响方法调用的对象,实质上使函数的第一个参数成为const对象的指针。而函数和方法参数控制着它的签名(除了其他东西,比如它的名字,是否是static等)。

您更具体地问:

  

在同一行中,并不是一个提供非变异函数的实现,它满足只要求变异函数的接口。

即使语言可以(并且我认为它在某些情况下确实存在)解析为const版本,它仍然需要为非const版本提供实现,因为是声明为纯虚拟的方法。

如果非const也可用时调用const版本,请考虑以下事项:

class Shape {
public:
    virtual double getArea() {
        std::cout << "non-const getArea" << std::endl;
        return static_cast<const Shape *>(this)->getArea();
    }
    virtual double getArea() const = 0;
};

与您示例中的Rectangle相结合。然后:

Rectangle *r = new Rectangle(4,2);
Shape *s = r;
r->getArea(); // calls const version
s->getArea(); // call non-const version

答案 3 :(得分:2)

大多数答案都说明为什么不能根据当前的语言规则来解释,而不是说为什么规则是这样编写的。我会试着回答为什么规则不能像你建议的那样。

Stroustrup的 Design&amp; C ++ 书的演变描述了为什么放宽重写规则以允许协变返回,这在C ++中并不总是允许的。因此,对您的问题的一个答案是,最初的覆盖必须是签名的完全匹配,并且对于不削弱虚函数的合同的“兼容”返回类型进行了例外。他们可能没有进一步放松,因为没有人想到它或没有人提出它。 D&amp; E确实提到了压倒一切规则的其他可能的放松,但他们说:“我们认为通过覆盖允许这种转换的好处不会超过实施成本和混淆用户的可能性。”这是相关的,因为我认为你的想法有很多混淆用户的可能性,并且实际上可能导致安全问题,特别是它削弱了类型系统。

考虑:

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成员函数,但实际上它是一个虚函数,所以它调用{em>非const <{1}} em>所以它试图修改一个const对象,它可以存储在只读内存中,因此会导致段错误。

这只是一个例子,说明在Square::getArea()示例中允许覆盖放松可能会导致未定义的行为,我相信在更现实的代码中可能存在更大的,可能更微妙的问题。

您可能会争辩说编译器不应该允许非const函数覆盖Shape,因此应该拒绝Rectangle::getArea(“一旦虚函数变为常量,它就无法返回”)但这会使等级制度变得非常脆弱。使用具有不同const的Square::getArea函数添加或删除中间基类将改变getArea是覆盖还是重载。虚拟函数已经存在一些这样的脆弱性,特别是协变回报,但根据D&amp; E Stroustrup认为协变回报有用,因为“放松允许人们在类型系统中做一些重要的事情,而不是使用强制转换。”我不认为允许const函数覆盖非常量的函数在类型系统中很好地适应,并且不允许做任何重要的事情,并且不会摆脱强制转换以允许使用新的(安全)技术

答案 4 :(得分:1)

可以重载方法,使其版本为const,而版本不是const。 (由于返回类型不是方法的唯一签名的一部分,并且方法可能不接受任何参数,const X*可以是唯一的方式来区分返回的内容,比方说, X*const。)

如果编译器没有要求您对此进行准确,那么有人可以向基类添加非{{1}}版本,突然您将覆盖其他方法。

答案 5 :(得分:0)

已经提到了const部分,所以我跳过这个。 我们假设您将基类视为您希望确保履行的合同。 由于函数的const和非const版本可以共存并且应该是coextist,因此如果您不履行给定的合同,则应该告诉您。

想象一下具有const和非const方法的基类,例如:一个带有operator []的容器,两种口味。现在你继承但不提供这两个功能。 您的孩子没有履行合同,也没有提供所需的功能。所以你应该得到一个错误,因为你的孩子可能无法通过多态实现