C++中的纯/ const函数

Alb*_*ert 14 c++ gcc const g++

我正在考虑在我的C++代码中更多地使用pure/const函数.(GCC中的pure/const属性)

但是,我很好奇我应该对它有多严格,哪些可能会破坏.

最明显的情况是调试输出(无论采用何种形式,都可以在cout,某些文件或某些自定义调试类中).我可能会有很多功能,尽管有这种调试输出,但它们没有任何副作用.无论是否进行调试输出,这绝对不会影响我的应用程序的其余部分.

或者我想到的另一种情况是使用一些SmartPointer类,它可能在调试模式下在全局内存中做一些额外的事情.如果我在pure/const函数中使用这样的对象,它确实会有一些轻微的副作用(在某种意义上说某些内存可能会有所不同),但它们不应该有任何真正的副作用(从某种意义上说,行为是在任何方式不同).

类似于互斥和其他东西.我可以想到许多复杂的情况,它有一些副作用(从某种意义上说,某些内存会有所不同,甚至可能会创建一些线程,进行某些文件系统操作等),但没有计算差异(所有这些副作用)很可能被遗漏,我甚至更愿意).

因此,总而言之,我想将函数标记为纯/ const,严格意义上不是纯/ const.一个简单的例子:

int foo(int) __attribute__((const));

int bar(int x) {
   int sum = 0;
   for(int i = 0; i < 100; ++i)
       sum += foo(x);
   return sum;
}

int foo_callcounter = 0;

int main() {
   cout << "bar 42 = " << bar(42) << endl;
   cout << "foo callcounter = " << foo_callcounter << endl;
}

int foo(int x) {
   cout << "DEBUG: foo(" << x << ")" << endl;
   foo_callcounter++;
   return x; // or whatever
}
Run Code Online (Sandbox Code Playgroud)

注意,严格意义上的函数foo不是const.尽管如此,foo_callcounter到底是什么并不重要.如果没有调试语句也没关系(如果没有调用该函数).

我期待输出:

DEBUG: foo(42)
bar 42 = 4200
foo callcounter = 1
Run Code Online (Sandbox Code Playgroud)

没有优化:

DEBUG: foo(42) (100 times)
bar 42 = 4200
foo callcounter = 100
Run Code Online (Sandbox Code Playgroud)

两种情况都完全正常,因为对我的用例来说唯一重要的是bar(42)的返回值.

它在实践中如何运作?如果我将这些函数标记为pure/const,它是否会破坏任何东西(考虑到代码都是正确的)?


请注意,我知道某些编译器可能根本不支持此属性.(顺便说一句,我在这里收集它们.)我也知道如何以代码保持可移植的方式使用thes属性(通过#defines).此外,我感兴趣的所有编译器都以某种方式支持它; 所以我不关心我的代码是否运行速度慢,编译器没有.

我也知道优化的代码可能看起来会有所不同,具体取决于编译器甚至编译器版本.


非常相关也是这篇LWN文章"纯粹和恒定功能的含义",特别是"秘籍"一章.(感谢ArtemGr提示.)

Kon*_*lph 17

我正在考虑在我的C++代码中更多地使用pure/const函数.

那是一个滑坡.这些属性是非标准的,它们的好处主要限于微优化.

这不是一个很好的权衡.编写干净的代码,不要应用这样的微优化,除非你仔细分析并且没有办法解决它.或者完全没有.

请注意,原则上这些属性非常好,因为它们明确地为编译器和程序员声明了函数的隐含假设.那很好.但是,还有其他方法可以明确地做出类似的假设(包括文档).但由于这些属性是非标准的,因此它们在普通代码中没有位置.它们应限于在性能关键库中非常明智地使用,其中作者试图为每个编译器发出最佳代码.也就是说,作者意识到只有GCC可以使用这些属性,并为其他编译器做出了不同的选择.

  • 这就是``define`的用途,因此您可以为将来的程序员记录代码的这些方面,并让编译器在支持的秘密. (4认同)
  • 我看到那些愚蠢的挫败者再次出局 - 被投票支持. (3认同)
  • 编译器*无法在所有情况下找出副作用自由度.Esp,如果代码在某个其他cpp文件中,并且您不使用链接器优化(我不确定链接器优化是否甚至这样做). (2认同)

ybu*_*ill 2

我期望输出:

我期望输入:

int bar(int x) {
   return foo(x) * 100;
}
Run Code Online (Sandbox Code Playgroud)

你的代码对我来说实际上看起来很奇怪。作为维护者,我认为要么foo实际上有副作用,要么更有可能立即将其重写为上述函数。

实践中效果如何?如果我将此类函数标记为 pure/const,它会破坏任何内容吗(考虑到代码都是正确的)?

如果代码全部正确,则不会。但你的代码正确的可能性很小。如果您的代码不正确,那么此功能可以掩盖错误:

int foo(int x) {
    globalmutex.lock();
    // complicated calculation code
        return -1;
    // more complicated calculation
    globalmutex.unlock();
    return x;
}
Run Code Online (Sandbox Code Playgroud)

现在给出上面的栏:

int main() {
    cout << bar(-1);
}
Run Code Online (Sandbox Code Playgroud)

这会终止,__attribute__((const))但否则会出现死锁。

这也很大程度上取决于实施。例如:

void f() {
    for(;;) 
    {
        globalmutex.unlock();
        cout << foo(42) << '\n';
        globalmutex.lock();
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器应该将调用移到哪里foo(42)?是否允许优化此代码?不是一般情况下!因此,除非循环真的很微不足道,否则您的功能不会带来任何好处。但如果你的循环很简单,你可以轻松地自己优化它。

编辑:由于阿尔伯特要求一个不太明显的情况,所以它来了:F 或例如,如果您实现operator <<ostream,则使用 ostream::sentry 来锁定流缓冲区。假设您在释放f 之后或锁定之前调用 pure/const 。有人使用此运算符cout << YourType()f也使用cout << "debug info". 根据您的说法,编译器可以自由地将调用f放入临界区。发生死锁。