c++ - 为什么不允许在 const 非 volatile 成员函数上消除公共(public)子表达式?

标签 c++ c++11 compiler-optimization

C++ 的目标之一是允许用户定义类型的行为与内置类型一样好。这似乎失败的一个地方是编译器优化。如果我们假设 const 非 volatile 成员函数在道德上等同于读取(对于用户定义的类型),那么为什么不允许编译器消除对此类函数的重复调用呢?例如

class C {
...
public:
    int get() const;
}

int main() {
    C c;
    int x{c.get()};
    x = c.get(); // why not allow the compiler to eliminate this call
}

允许这样做的论点与复制省略的论点相同:虽然它改变了操作语义,但它应该适用于遵循良好语义实践的代码,并在效率/模块化方面提供实质性改进。 (在这个例子中,它显然很愚蠢,但它变得非常有值(value),比如说,在函数被内联时消除冗余的迭代安全检查。)

当然,对于返回非 const 引用的函数,只允许返回值或 const 引用的函数允许这样做是没有意义的。

我的问题是是否有一个基本的技术论点反对这一点并不同样适用于复制省略。

注意:为了清楚起见,我并不是建议编译器查看 get() 的定义内部。我是说 get() 本身的声明应该允许编译器省略额外的调用。我并不是说它保留了假设规则;我声称,就像在复制省略中一样,这是我们希望允许编译器违反假设规则的情况。如果您在编写代码时希望副作用在语义上可见,并且不希望消除冗余调用,则不应将您的方法声明为 const。

最佳答案

基于对问题的澄清的新答案

C::get 需要比 const 更强的注解。就目前而言,const 是一种 promise ,即该方法不会(概念上)修改对象。它不保证与全局状态或副作用的交互。

因此,如果新版本的 C++ 标准为 as-if 规则开辟了另一个异常(exception),就像它对复制省略所做的那样,仅基于一个方法被标记为 const 的事实,它将破坏许多现有代码.标准委员会似乎非常努力地尝试不破坏现有代码。

(复制省略也可能破坏了一些代码,但我认为与您提议的相比,这实际上是一个非常小的异常(exception)。)

您可能会争辩说我们应该重新指定 const 在方法声明中的含义,赋予它更强的含义。这将意味着您不能再拥有常量的 C::print 方法,因此这种方法似乎也会破坏许多现有代码。

所以我们必须发明一个新的注释,比如 pure_function。要将其纳入标准,您必须提出它并可能说服至少一个编译器制造商将其作为扩展来实现,以说明它的可行性和有用性。

我怀疑增量实用程序非常低。如果您的 C::get 是微不足道的(没有与全局状态的交互并且没有可观察到的副作用),那么您最好在类定义中定义它,从而使其可用于内联。我相信内联将允许编译器生成与声明中的 pure_function 标记一样最佳的代码(甚至可能更多),因此我不希望 pure_function 标记的增量好处足以说服标准委员会,编译器制造商和语言用户采用它。

原始答案

C::get 可能依赖于全局状态并且它可能具有可观察到的副作用,其中任何一个都会导致忽略第二次调用的错误。这将违反假设规则。

问题是编译器在调用站点进行优化时是否知道这一点。在编写示例时,只有 C::get 的声明在范围内。定义在别处,大概在另一个编译单元中。因此,编译器在编译和优化调用代码时必须假设最坏的情况。

现在,如果 C::get 的定义既微不足道又在 View 中,那么我想编译器理论上有可能实现没有副作用或非确定性行为,但我怀疑大多数优化器会变得如此激进。除非 C::get 是内联的,否则我想分析的路径会呈指数级增长。

如果您想跳过整个赋值语句(而不是仅仅第二次调用 C::get),那么编译器还必须检查赋值运算符的副作用,并且依赖全局状态以确保优化不会违反假设规则。

关于c++ - 为什么不允许在 const 非 volatile 成员函数上消除公共(public)子表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23722601/

相关文章:

c++ - 使用基于范围的 for 迭代图的边缘

c++ - 限制可变参数函数模板仅接受一个可变参数类模板的嵌套可变参数类模板的变体?

haskell - 函数式语言中的部分求值和函数内联有什么区别?

c++ - Multipart WADO-RS dicom数据解析

c++ - 没有默认构造函数的类数组的运算符 new

C++ 快速排序段错误

c++ - C++ 编译器如何在 C++0x 中实现线程本地存储?

go - 什么是 Go cmd 选项 'gcflags' 所有可能的值

c++ - 在 C++ 中总是调用空构造函数吗?

c++ - 如何在推导上下文中将 std::bind (或 lambda)转换为 std::function ?