C ++:将构造函数调用到临时对象

时间:2011-11-27 16:23:53

标签: c++ optimization

假设我有以下内容:

int main() {
    SomeClass();
    return 0;
}

如果没有优化,将调用SomeClass()构造函数,然后调用它的析构函数,并且对象将不再存在。

但是,根据IRC通道,如果编译器认为对SomeClass构造函数/析构函数没有副作用,那么构造函数/析构函数调用可能会被优化掉。

我认为显而易见的方法是不使用一些构造函数/析构函数(例如使用函数或静态方法等),但有没有办法确保调用构造函数/析构函数?

3 个答案:

答案 0 :(得分:7)

  

但是,根据IRC频道,如果编译器认为对SomeClass构造函数/析构函数没有副作用,那么构造函数/析构函数调用可能会被优化掉。

粗体部分错误。应该是:知道 没有可观察行为

E.g。来自最新标准的§1.9(有更多相关的引用):

  

执行格式良好的程序的符合实现应产生相同的可观察行为   作为具有相同程序的抽象机的相应实例的可能执行之一   和相同的输入。但是,如果任何此类执行包含未定义的操作,则此国际   标准不要求使用该输入执行该程序的实现(甚至不是   关于第一次未定义操作之前的操作。)

事实上,这整个机制支撑着最普遍存在的C ++语言习语: Resource Acquisition Is Initialization

<强> 新闻背景

让编译器优化掉琐碎的case-constructors 非常有帮助。它允许迭代器编译为与使用原始指针/索引器完全相同的性能代码。

它还允许函数对象编译为与内联函数体完全相同的代码。

这使得C ++ 11 lambdas 完美最适合简单的用例:

factorial = std::accumulate(begin, end, [] (int a,int b) { return a*b; });

lambda编译为类似于

的仿函数对象
struct lambda_1
{
     int operator()(int a, int b) const 
     { return a*b; }
};

编译器看到构造函数/析构函数可以被省略并且函数体被内联。最终结果是最优 1


更多(不)可观察的行为

该标准包含一个非常有趣的例子,相反,激发你的想象力。

  

§20.7.2.2.3

     

[ Note:临时对象构造和销毁导致的使用计数更新不是   可观察到的副作用,因此实施可能会影响效果(和隐含的保证)   不同的手段,没有创造一个临时的。特别是在示例中:

shared_ptr<int> p(new int);
shared_ptr<void> q(p);
p = p;
q = p;
     

这两项任务可能都是无操作。 —end note ]

IOW:不要低估优化编译器的能力。这绝不意味着语言保证会被抛到窗外!

1 虽然可以有更快的算法来获得阶乘,但取决于问题域:)

答案 1 :(得分:0)

我确定'SomeClass :: SomeClass()'没有实现为'inline',编译器无法知道构造函数/析构函数没有副作用,它总是会调用构造函数/析构函数。

答案 2 :(得分:0)

如果编译器正在优化构造函数/析构函数调用的可见效果,那么它就是错误的。如果它没有明显效果,那么你无论如何都不应该注意到它。

但是,让我们假设你的构造函数或析构函数确实具有可见效果(因此构造和随后对该对象的破坏实际上不是无操作),编译器可以合理地认为它不会(并不是说我能想到这种情况,但那时,我可能只是缺乏想象力)。然后,以下任何策略都应该有效:

  • 确保编译器无法查看构造函数和/或析构函数的定义。如果编译器不知道构造函数/析构函数的作用,则不能认为它没有效果。但请注意,这也会禁用内联。如果您的编译器不进行跨模块优化,只需将构造函数/析构函数放入另一个文件就足够了。

  • 确保您的构造函数/析构函数确实具有可观察的行为,例如通过使用volatile变量(每次读或写一个volatile变量都被认为是C ++中的可观察行为)。

然而,让我再次强调,你不可能做任何事情,除非你的编译器是非常错误的(在这种情况下我强烈建议你改变编译器: - ))。