【问题标题】:In C++, can I declare a reference so as to indicate that nothing will ever modify it?在 C++ 中,我可以声明一个引用以表明不会修改它吗?
【发布时间】:2015-02-02 15:39:12
【问题描述】:

如果我这样做

typedef void Cb();

int foo(int const& a, Cb cb) {
  int x = a;
  cb();
  return x - a;
}

g++ -O3 -save-temps -c foo.cpp编译,我看到减法被保留了,而如果cb();被注释掉,整个函数优化为

xorl    %eax, %eax

我可以对参数a 的规范做些什么,以便无论调用cb(),并且不强制a 成为唯一引用(即它可能会在其他地方被引用,但不会通过这些引用对其进行修改)?

【问题讨论】:

  • 您可以按值获取参数 (int a),这将向编译器保证没有任何东西可以改变它。我也不确定您所说的“唯一参考”是什么意思。
  • const int x = a 会发生什么?
  • @pepper_chico a 仍然可以引用通过调用cb() 可能会更改的内容,因此减法仍然无法优化。
  • @sjdowling 标准中没有任何内容要求引用以与指针相同的方式实现;引用只是对象的另一个名称。
  • 有“纯”函数的概念,但不规范。 gcc 有一个为此目的的扩展:stackoverflow.com/q/9441262 结果正如预期的那样,就像没有函数调用一样:coliru.stacked-crooked.com/a/0639d89c013ae02f

标签: c++ c++11 optimization reference


【解决方案1】:

__restrict 扩展,你可以在gcc.godbolt.org 上试试这个:

typedef void Cb();

int foo(const int & __restrict a, Cb cb) {
  int x = a;
  cb();
  return x - a;
}

奇怪的是,only clang does the optimizationgcc doesn't do it


请注意,类似限制的别名被认为是 C++ 标准的一部分:

也许将来你可以按标准来做。

【讨论】:

  • 我认为restrict 在这里不起作用:它只是说给定块范围内的两个引用不别名。这里的问题是a 可能与全局变量别名,而restrict 不能用于全局变量。另见:stackoverflow.com/a/30827880/895245
【解决方案2】:

执行建议的优化是不正确的,因为其余代码可能是:

static int var;

void func()
{
    var++;
}

// ...
foo(var, func);

我不知道您可以设置任何特定于编译器的属性来表示 cb() 不会修改 a

【讨论】:

【解决方案3】:

你为什么不把int x = a; 行移到函数调用下面呢?

如果cb() 既不影响x 也不影响a,你应该没问题,我认为编译器会再次优化调用,因为x 不能在两个调用之间改变。如果您无法重新排序这两个调用是有原因的,那么您可能一开始就无法对其进行优化。

这不是您可以提示编译器执行的操作,因为无法保证在调用 cb() 之后 x 和 a 都没有改变。

将此视为读/写访问的顺序。如果在cb() 期间没有对ax 进行读写访问,您可以手动重新排序函数调用。

如果写入ax,则无法重新排序,优化将不正确。

如果x被读取,你不能重新排序函数调用,但如果x真的只是被读取,你可以从a读取,并且只在调用之后定义x,因为它将具有相同的值作为a,你有没有在通话前声明过。

【讨论】:

    【解决方案4】:

    如果您的编译器支持该扩展,您可以在引用中使用 C restrict
    (一些编译器允许__restrict__restrict__,它们是实现命名空间的一部分。)

    这是您对编译器的承诺,即该对象在任何地方都没有别名,因此可以对其进行优化。
    如果你对编译器撒谎,那么,你会得到你应得的损坏代码。

    【讨论】:

    • 谢谢,这是一个有用的知识。似乎restrict 的规范,如果我理解正确的话,不仅要求a 不会在其他地方被修改,而且它也不会在其他地方被提及。理想情况下,我希望 a 可以有许多其他引用,其中没有一个会修改底层对象。
    • 好吧,如果对象没有被其中任何一个修改,那么有别名并不重要。至少对于程序的正确性。
    • 如果我对 this answer 的理解正确,那可能很重要。
    • @Owen:这个答案实际上并没有这么说。看,他提到的可以(不能)重叠的数组之一被显式修改了。
    • gcc 支持__restrict,但在这种情况下添加它根本不会改变编译的代码。至少int foo(int const& __restrict a, Cb cb) { ... }
    【解决方案5】:

    __attribute__((const)) 就足够了

    如记录在:https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Function-Attributes.html 中,它告诉编译器给定函数不会修改全局变量(尽管它可以读取它们)。

    还有constpure 子集,它也禁止全局读取。

    考虑以下简化示例:

    int __attribute__((const)) g();
    
    int f(int a) {
      int x = a;
      g();
      return x - a;
    }
    

    g++ 4.8 x86_64 -O3 上,它编译为:

    • xor %eax,%eaxconst
    • 不假设a的大函数没有const不修改

    似乎没有更细粒度的属性表明函数不会根据需要修改 given 变量。

    __attribute__((const)) 可以应用于函数指针吗?

    让我们试试吧:

    int f(int& a, void __attribute__((const)) (*g)(void)) {
        int x = a;
        (*g)();
        return x - a;
    }
    

    再一次,它编译为xor %eax,%eax,并编译为一个没有const 的大函数,所以答案是肯定的。

    这个语法被问到:Function pointer to __attribute__((const)) function?

    它也在 2011 年出现在邮件列表中:http://comments.gmane.org/gmane.comp.gcc.help/38052 至少在当时,它只对某些属性有效。

    可以将 __attribute__((const)) 添加到 typedef 中吗?

    这行得通:

    typedef void __attribute__((const)) (*g_t)(void);
    
    int f(int& a, g_t g) {
        int x = a;
        (*g)();
        return x - a;
    }
    

    或:

    typedef void (g_t)(void);
    
    int f(int& a, g_t __attribute__((const)) g) {
        int x = a;
        g();
        return x - a;
    }
    

    但我找不到将属性放在 typedef 上的方法,传递一个函数,而不是一个指针,例如:

    typedef void __attribute__((const)) (g_t)(void);
    

    GCC 给出一个警告,指出在这种情况下该属性被忽略。

    【讨论】:

      猜你喜欢
      • 2011-12-10
      • 1970-01-01
      • 2017-10-09
      • 2010-10-01
      • 2016-02-29
      • 2011-05-22
      • 1970-01-01
      • 2019-05-06
      相关资源
      最近更新 更多