【问题标题】:Return 'this' as non-const from const member function从 const 成员函数返回 'this' 作为非常量
【发布时间】:2012-11-10 12:36:34
【问题描述】:

我想在下面的 Point 类上做方法链接。

#include <iostream>

class Point {
  public:
    Point(int x, int y): _x(x), _y(y) {}

    Point& moveX(int x);
    Point& moveY(int y);
    Point& print() const;

  ...
};

...

Point& Point::print() const {
  std::cout << "(" << _x << "," << _y << ")" << std::endl;
  return *this;  // Compile fails
}

我认为将print() 标记为const 成员函数是有意义的,因为它只打印内部成员。但是,我想在非 const 和 const 函数之间进行方法链接,如下所示。

int main() {
  Point p(1,1);
  p.moveX(10).print().moveY(11); // method chaining
}

所以我必须将 this 作为非常量返回,但编译失败,因为据我了解,成员在 const 成员函数中标记为 const,包括 this

有没有办法在这种情况下进行方法链接?

【问题讨论】:

  • 键盘有问题吗?
  • 我的问题有什么问题?
  • 好吧,如果你声明一个方法const 你不能返回非常量引用/指针。否则,您可能会像在最后一行中所做的那样破坏 constness。这就是编译器不信任你的原因
  • @teerapap - 为什么不把它分成一系列的行呢?它将有助于调试
  • 为什么不增加几行代码并坚持OOP?

标签: c++ method-chaining


【解决方案1】:

您可以提供两个成员函数,一个const 和一个非constconst 将在Point const 上调用,非constPoint 上调用。

class Point {
public:
    Point(int x, int y): _x(x), _y(y) {}

    Point& moveX(int x);
    Point& moveY(int y);
    Point& print();
    Point const& print() const;

    ...
};

作为侧节点,最好重载std::ostream&amp; operator&lt;&lt;(std::ostream&amp;, Point const&amp;),这样你就可以将它用于任何输出流,而不仅仅是std::cout

class Point {
    ...
private:
    friend std::ostream& operator<<(std::ostream& stream, Point const& point) {
        stream << "(" << point._x << "," << point._y << ")";
        return stream;
    }
}

【讨论】:

  • 这对我来说似乎也是一个可以接受的答案。感谢旁注。
  • 并且非常量可以实现为例如Point&amp; print() { static_cast&lt;const Point *&gt;(this)-&gt;print(); return *this; }。因此,如果您担心,无需复制任何打印代码。
【解决方案2】:

IMO 你没有正确理解 const 方法可以做什么。如果 const 方法返回对您的对象的非常量引用,则它不是常量方法。

因此,在您的情况下,您可以简单地从 print 方法中返回任何内容,并在链接结束时使用它,例如 p.moveX(10).moveY(11).print();.

UPD。或者,如果您有可能向您的类添加一些新的 const 方法,您可以返回 const Point&amp;

【讨论】:

  • 我认为'const'方法告诉调用者这个方法不会改变对象。我不知道它的范围包括返回的对象。
  • @teerapap 返回 this 的非常量引用打开了间接改变对象的可能性(从调用方法的地方)。那就是 const 方法一定不能给这种可能性。
【解决方案3】:

您可以在返回时使用const_cast(即return const_cast&lt;Point&amp;&gt;(*this)) - 这将确保您的方法无法修改对象,但调用者将能够修改它。

【讨论】:

  • 我不建议这样做,因为正如您所说,它允许调用者编写类似const Point p(1,2); p.print().moveX(1); 的东西,它具有未定义的行为。你可以争辩说这是调用者的错,调用者可以争辩说你不应该写一个 const-unsafe 接口。由于编写一个 const 安全的版本很容易,因此您牺牲了编译时 const 检查的好处,以换取将代码大小减少一个小函数。
  • 同意-实际上我在发布答案并阅读了您正在谈论的未定义行为后对const_cast 进行了一些研究,但已经有一个公认的答案(几乎相同)所以决定不编辑。不过感谢您的注意。
【解决方案4】:

失败的原因是在const成员函数内部,this实际上是const Point*,而不是Point*。因此,您正在尝试从 const 指针初始化非 const 引用。并不是编译器不相信你,你只是一次要求两个不兼容的东西。

在我看来,这是const_cast极少数有效用法之一。通常,使用const_cast 几乎总是表示设计错误,或者更糟糕的是编程错误。
在这里,函数真的是const,应该是const,但是没有理由你不能在之后链接非const的东西,所以做这样的事情可以说是合法的。

但是请注意,尽管该函数是严格意义上的const(就对象而言,与其说是使用 IO 函数!),但您应该考虑的一件事是,在某些(罕见的)情况下,它可能会导致代码不符合您的要求。允许编译器缓存 const 函数的结果并省略对同一 const 函数的另一个调用(因为您承诺它不会改变任何内容)。因此,可以将some_point.Print().Print(); 优化为some_point.Print()。这对您来说可能不是问题(为什么要打印两次相同的值),只是一般需要注意的事情。

【讨论】:

  • “允许编译器缓存 const 函数的结果并省略对同一 const 函数的另一个调用(因为您承诺它不会改变任何东西)”是不正确的。 const 函数承诺不会改变任何东西,这甚至不是真的。
  • 这是一个非常迂腐的反对意见。 const 成员函数承诺客户端可观察状态不会改变(来自 C++ FAQ 的措辞),或者不那么雄辩地表明 this 指向的对象不能被此函数修改。这显然不能阻止您通过别名this 恶意修改对象,但那又怎样。没有人能阻止你对编译器撒谎,抱怨是没有意义的。 C++ 语言也不能阻止您从不同的线程修改它的任何对象。
  • 一点也不迂腐,您的回答包含对const 的严重误解,并且会导致您编写的编译器实际上被破坏。不允许编译器假定const 成员函数不会更改对象。在为类型定义“客户端可观察状态”时,文档化接口通常会保证这一点。但是标准并没有说因为调用const成员函数而修改数据成员是UB,它只是说修改const object是UB。编译器不能假设你设计了一个合理的 API。
  • 此外,除了修改对象之外,编译器不能省略对 const 函数的第二次调用,因为该函数可能除了修改对象之外还有副作用(在这种情况下,产生输出)。编译器不能忽略这些。 GCC 让您将函数标记为“纯”,然后它更像您所说的。
猜你喜欢
  • 2021-08-10
  • 1970-01-01
  • 1970-01-01
  • 2019-05-04
  • 1970-01-01
  • 2019-07-09
  • 1970-01-01
相关资源
最近更新 更多