50

我已经阅读了所有关于 C++ 中 const 正确性的建议,并且它很重要(部分)因为它可以帮助编译器优化你的代码。我从未见过关于编译器如何使用这些信息来优化代码的很好的解释,即使是好书也没有继续解释幕后发生的事情。

例如,编译器如何优化声明为 const 的方法与不是但应该声明的方法。当你引入可变变量时会发生什么?它们会影响 const 方法的这些优化吗?

4

12 回答 12

56

我认为 const 关键字主要是为了程序语义的编译检查而引入的,而不是为了优化。

Herb Sutter 在GotW #81 文章中很好地解释了为什么编译器在通过 const 引用传递参数或声明 const 返回值时无法优化任何内容。原因是编译器无法确保引用的对象不会被更改,即使声明为 const :可以使用 const_cast,或者某些其他代码可以在同一对象上具有非常量引用。

但是,引用 Herb Sutter 的文章:

[只有] 一种情况,说“const”确实意味着什么,那就是当对象在它们被定义的时候变成 const 时。在这种情况下,编译器通常可以成功地将此类“真正的 const”对象放入只读内存 [...]。

这篇文章还有很多内容,所以我鼓励你阅读它:之后你会对不断优化有更好的理解。

于 2008-10-17T14:30:37.427 回答
35

让我们忽略方法,只看 const 对象;编译器在这里有更多的优化机会。如果一个对象被声明为 const,那么 (ISO/IEC 14882:2003 7.1.5.1(4)):

除了可以修改任何声明为 mutable (7.1.1) 的类成员外,任何在 const 对象的生命周期 (3.8) 期间修改它的尝试都会导致未定义的行为。

让我们忽略可能具有可变成员的对象 - 编译器可以自由假设该对象不会被修改,因此它可以产生显着的优化。这些优化可以包括以下内容:

  • 将对象的值直接合并到机器指令操作码中
  • 完全消除由于 const 对象用于编译时已知的条件表达式而永远无法达到的代码
  • 如果 const 对象控制循环的迭代次数,则循环展开

请注意,这些内容仅适用于实际对象为 const - 它不适用于通过 const 指针或引用访问的对象,因为这些访问路径可能导致非 const 对象(它甚至可以通过 const 更改对象指针/引用,只要实际对象是非常量的,并且您抛弃了对象访问路径的常量性)。

在实践中,我认为没有编译器可以对所有类型的 const 对象进行任何重大优化。但是对于原始类型的对象(整数、字符等),我认为编译器可以非常积极地优化这些项目的使用。

于 2008-10-17T15:41:05.787 回答
6

挥手开始

本质上,数据越早修复,编译器就越能围绕数据的实际分配移动,确保管道不会停止

结束挥手

于 2008-10-17T14:03:08.527 回答
5

嗯。常量正确性更像是一种样式/错误检查,而不是优化。一个全面优化的编译器将跟踪变量的使用,并且可以检测变量何时有效地为 const 或不是。

除此之外,编译器不能依赖你说真话——你可能会在它不知道的库函数中丢弃 const。

所以是的,const-correctness 是一个值得追求的目标,但它不会告诉编译器任何它自己无法弄清楚的事情,假设一个好的优化编译器。

于 2008-10-17T14:14:56.243 回答
3

它不会优化声明为 const 的函数。

它可以优化调用声明为 const 的函数的函数。

void someType::somefunc();

void MyFunc()
{
    someType A(4);   // 
    Fling(A.m_val);
    A.someFunc();
    Flong(A.m_val);
}

这里要调用 Fling,必须将值 A.m_val 加载到 CPU 寄存器中。如果 someFunc() 不是 const,则必须在调用 Flong() 之前重新加载该值。如果 someFunc 是 const,那么我们可以使用仍在寄存器中的值调用 Flong。

于 2008-10-17T14:11:02.833 回答
3

将方法设置为 const的主要原因是为了 const 的正确性,而不是为了可能对方法本身进行编译优化。

如果变量是 const 它们可以(理论上)被优化掉。但只有编译器可以看到范围。毕竟编译器必须允许它们在别处使用 const_cast 进行修改。

于 2008-10-17T14:13:46.713 回答
2

这些都是真实的答案,但答案和问题似乎假设一件事:编译器优化实际上很重要。

只有一种代码对编译器优化很重要,即在代码中

  • 紧密的内环,
  • 在您编译的代码中,而不是第 3 方库中,
  • 不包含函数或方法调用(甚至是隐藏的),
  • 程序计数器花费大量时间的地方

如果其他 99% 的代码被优化到第 N 级,它不会有太大的不同,因为它只在程序计数器实际花费时间的代码中很重要(您可以通过采样找到)。

于 2008-12-23T20:23:00.677 回答
1

如果优化器实际上将大量库存放入 const 声明中,我会感到惊讶。有很多代码最终会抛弃 const-ness,这将是一个非常鲁莽的优化器,它依赖于程序员声明来假设状态何时可能改变。

于 2008-10-17T14:15:28.363 回答
1

const-correctness is also useful as documentation. If a function or parameter is listed as const, I don't need to worry about the value changing out from under my code (unless somebody else on the team is being very naughty). I'm not sure it would be actually worth it if it wasn't built into the library, though.

于 2008-12-23T21:25:25.887 回答
0

直接优化最明显的一点const是将参数传递给函数。确保函数不会修改数据通常很重要,因此函数签名的唯一真正选择是:

void f(Type dont_modify); // or
void f(Type const& dont_modify);

当然,这里真正的魔力是传递引用而不是创建对象的(昂贵的)副本。但是如果引用没有被标记为const,这会削弱这个函数的语义并产生负面影响(例如使错误跟踪更难)。因此,const在此处启用优化。

/EDIT:实际上,一个好的编译器可以分析函数的控制流,确定它不会修改参数并进行优化(传递引用而不是副本)本身。const这只是对编译器的帮助。然而,由于 C++ 有一些相当复杂的语义,而且这样的控制流分析对于大函数来说可能非常昂贵,我们可能不应该依赖编译器。有没有人有任何数据支持我/证明我错了?

/EDIT2:是的,一旦自定义复制构造函数开始发挥作用,它就会变得更加棘手,因为不幸的是,编译器不允许在这种情况下省略调用它们。

于 2008-10-17T14:58:08.057 回答
0

这段代码,

class Test
{
public:
  Test (int value) : m_value (value)
  {
  }

  void SetValue (int value) const
  {
    const_cast <Test&>(*this).MySetValue (value);
  }

  int Value () const
  {
    return m_value;
  }

private:
  void MySetValue (int value)
  {
    m_value = value;
  }

  int
    m_value;
};

void modify (const Test &test, int value) 
{
  test.SetValue (value);
}

void main ()
{
  const Test
    test (100);

  cout << test.Value () << endl;
  modify (test, 50);
  cout << test.Value () << endl;
}

输出:

100
50

这意味着 const 声明的对象已在 const 成员函数中更改。C++ 语言中 const_cast(和 mutable 关键字)的存在意味着 const 关键字不能帮助编译器生成优化的代码。正如我在之前的帖子中指出的那样,它甚至会产生意想不到的结果。

作为基本规则:

const != 优化

事实上,这是一个合法的 C++ 修饰符:

volatile const
于 2008-10-17T15:17:55.367 回答
-1

const帮助编译器优化主要是因为它使您编写可优化的代码。除非你扔进去const_cast

于 2008-10-17T18:51:09.803 回答