C++ 是现代的C++;在某些情况下,编译器能够避免两次调用const函数吗?

C++ 是现代的C++;在某些情况下,编译器能够避免两次调用const函数吗?,c++,optimization,compiler-construction,call,C++,Optimization,Compiler Construction,Call,例如,如果我有以下代码: class SomeDataProcessor { public: bool calc(const SomeData & d1, const SomeData & d2) const; private: //Some non-mutable, non-static member variables } SomeDataProcessor sdp; SomeData data1; SomeData data2; someObscureF

例如,如果我有以下代码:

class SomeDataProcessor
{
public:
    bool calc(const SomeData & d1, const SomeData & d2) const;
private:
    //Some non-mutable, non-static member variables
}

SomeDataProcessor sdp;
SomeData data1;
SomeData data2;

someObscureFunction(sdp.calc(data1, data2),
                    sdp.calc(data1, data2));

让我们考虑<强>潜在< /强>等效代码:

bool b = sdp.calc(data1, data2);
someObscureFunction(b,b);
为了使其有效,函数
calc()
应该满足一些要求,例如,我调用属性
\u pure\u const\u formula\u

一个纯常量公式将:

  • 不更改任何成员、静态或全局变量状态
  • 仅调用
    \u pure\u const\u formula\u
    函数
  • 也许还有其他一些我没想到的条件
例如,调用随机数生成器不符合这些要求


编译器是否允许用第二个代码替换第一个代码,即使它需要递归地挖掘调用的函数?现代编译器能够做到这一点吗?

不,给定所示的代码,编译器不能保证建议的优化不会有明显的差异,也没有现代编译器能够优化第二个函数调用

一个非常简单的例子:这个类方法可能使用一个随机数生成器,并将结果保存在一些私有缓冲区中,稍后代码的其他部分将读取该缓冲区。显然,现在消除函数调用会减少在该缓冲区中随机生成的值


换句话说,仅仅因为类方法是
const
并不意味着它在被调用时没有明显的副作用。

否,在这种情况下编译器不允许这样做。
const
仅表示不更改方法所属对象的状态。但是,使用相同的输入参数多次调用此方法可能会得到不同的结果。例如,考虑一个生成随机结果的方法。

GCC具有
pure
(用作
\uuuuu属性(pure))
,用于告诉编译器可以消除冗余调用的函数。例如在
strlen
上使用


我不知道有哪位编译器会自动执行此操作,尤其是考虑到要调用的函数可能没有源代码形式,并且对象文件格式不包含关于函数是否纯的元数据

是的,绝对是。

例如,如果您的所有函数都返回
true
,并且编译器在调用站点上可以看到它的定义,那么整个函数调用可能会被省略,只会导致:

someObscureFunction(true, true);
编译器拥有足够信息的程序可能会从相当复杂的任务链到一两条指令进行“优化”。现在,对成员变量的实际操作在某种程度上把优化程序推到了极限,但是如果变量是
私有的
,给定了一个已知的初始值,并且没有被任何其他成员函数变异,我不明白为什么一个编译器如果愿意的话不能直接内联它的已知值。编译器非常非常聪明


人们认为编译后的程序是源代码中行的一对一映射,但这几乎从来都不是真的。C++的全部目的是,当你的程序运行时,你的计算机实际上要做的抽象。

< P>是的,现代C编译器可以在调用冗余函数调用的情况下,并且只要它们能证明这样的优化行为遵循原始程序语义。例如,这意味着如果函数没有副作用并且其返回值仅取决于参数,则可以消除对具有相同参数的同一函数的多个调用

现在,您特别询问了
const
——这对开发人员非常有用,而不是对编码人员。
const
函数是一个提示,表示方法没有修改调用它的对象,
const
参数是一个提示,表示参数没有修改。但是,函数可以(合法地1)丢弃
指针或其参数的
常量。所以编译器不能依赖它

此外,即使传递给函数的
const
对象实际上从未在该函数中修改过,并且
const
函数从未修改过接收方对象,该方法也可以很容易地依赖可变的全局数据(并且可以变异此类数据)。例如,考虑返回当前时间的函数,或者递增全局计数器。

因此
const
声明帮助程序员,而不是编译器2

但是,编译器可能会使用其他技巧来证明调用是冗余的:

  • 函数可能与调用者位于同一个编译单元中,允许编译器检查它并准确确定它所依赖的内容。这种方法的最终形式是内联:可以将函数体移动到调用方中,此时优化器可以从以后的调用中删除冗余代码(最多包括这些调用中的所有代码,也可能包括原始调用的全部或端口)
  • 工具链可能会使用某种类型的链接时间优化,这有效地允许进行上文所述的分析类型,即使是针对不同编译单元中的函数和调用方。这允许在生成最终可执行文件时对任何代码进行优化
  • 编译器可能允许用户使用一个属性对函数进行注释,该属性通知编译器它可能将函数视为没有副作用。例如,gcc提供and
    const
    函数属性,通知
    gcc
    函数没有副作用,并且只依赖于它们的参数(在
    pure
    的情况下,依赖于全局变量)

1通常,只要对象最初不是定义为
const