繁体   English   中英

为什么不允许常量子表达式消除const非易失性成员函数?

[英]why not allow common subexpression elimination on const nonvolatile member functions?

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

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
}

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

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

我的问题是,是否存在针对此的基本技术论证,这同样适用于复制省略。

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

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

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

因此,如果C ++标准的新版本为as-if规则划分了另一个例外,就像复制省略那样,仅基于方法被标记为const的事实,它将破坏许多现有代码。 标准委员会似乎很难不破坏现有的代码。

(复制elision也可能破坏了一些代码,但我认为与你提议的相比,它实际上是一个非常狭窄的例外。)

你可能会争辩说我们应该重新指定const对方法声明的含义,赋予它更强的意义。 这意味着你不能再使用const的C::print方法,所以看起来这种方法也会破坏很多现有的代码。

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

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

原始答案

C::get可能依赖于全局状态,并且它可能具有可观察到的副作用,其中任何一种都会使得忽略第二次调用成为错误。 它会违反as-if规则。

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

现在,如果C::get的定义既微不足道又在视野中,那么我认为理论上编译器可以意识到没有副作用或非确定性行为,但我怀疑大多数优化器都具有攻击性。 除非C::get被内联,否则我认为分析的路径会呈指数增长。

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

首先,方法(或引用)的const -ness与优化器完全无关,因为constness可以合法地转换(使用const-cast),因为在引用的情况下,可能存在别名。 Const正确性旨在帮助程序员,而不是优化器(另一个问题是它是否真的有帮助,但这是一个单独的无关讨论)。

此外,为了省略对函数的调用,优化器还需要确保结果不依赖于并且不会影响全局状态。

编译器有时会有一种方法来声明函数是“纯粹的”,即结果只依赖于参数而不影响全局状态(如sin(x) ),但是如何声明它们是依赖于实现的,因为C ++标准不涵盖这个语义概念。

还需要注意的是这个词constconst reference描述不是引用对象的引用的属性。 没有人知道对象的常量,你给了一个const引用,当你的参考仍在手中时,对象确实可以改变甚至不存在。 const引用意味着您不能使用该引用更改对象,而不是对象是常量或者它将在一段时间内保持不变。

有关为什么const引用和值是两个非常不同的语义概念以及如果您混淆它们可以遇到的细微错误的原因的描述,请参阅此更详细的答案

Adrian McCarthy对你问题的第一个答案就是尽可能清楚:

成员函数的const -ness是一个承诺,即不会修改外部可见状态(例如,在对象实例中限制mutable变量)。

你会期望一个const成员函数,它只报告一个对象的内部状态,总是返回相同的答案。 然而,它也可以与不断变化的现实世界互动,并且每次都会返回不同的答案。

如果它是返回当前时间的函数怎么办?

让我们把它作为一个例子。

这是一个将时间戳(double)转换为人类可读字符串的类。

class time_str {
    // time and its format
    double time;
    string time_format;
public:
    void set_format(const string& time_format);
    void set_time(double time);
    string get_time() const;
    string get_current_time() const;
};

它是如此使用(笨拙):

time_str a;
a.set_format("hh:mm:ss");
a.set_time(89.432);
cout << a.get_time() << endl;

到现在为止还挺好。 每次调用a.get_time(); 将返回相同的结果。

但是,在某些时候,我们决定引入一个便利函数,它以相同的格式返回当前时间:

cout << a.get_time() << " is different from " << a.get_current_time() << endl;

它是const,因为它不会以任何方式改变对象的状态(尽管它访问时间格式)。 但是,显然每次调用get_current_time() 必须返回不同的答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM