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

Posted

技术标签:

【中文标题】C++:调用临时对象的构造函数【英文标题】:C++: Calling a constructor to a temporary object 【发布时间】:2011-11-27 16:23:53 【问题描述】:

假设我有以下内容:

int main() 
    SomeClass();
    return 0;

如果不优化,SomeClass()的构造函数会被调用,然后它的析构函数会被调用,对象就没有了。

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

我想解决这个问题的明显方法不是使用一些构造函数/析构函数(例如使用函数或静态方法等),但是有没有办法确保构造函数/析构函数的调用?

【问题讨论】:

如果它没有改变世界,你为什么还要执行它? C++ 编译器将 C++ 源代码转换为可执行形式。您已经为构造函数/析构函数编写了不执行任何操作的源代码,因此编译器生成了不执行任何操作的可执行代码。 我相信这是您使用哪种编译器的问题。例如,MSVC 编译器对临时对象非常宽容,可能不会优化此代码。 当我需要在函数中的某个点执行某个操作时,我会使用此模式,并且我想保证在我离开该函数时会发生另一个操作。例如:在分析代码中,我将有一个 Timer 对象,该对象将向管理器注册其自身及其构造开始时间,然后在析构函数上发出结束时间信号。该代码从未在 msvc/gcc/llvm 上为我优化过。我想如果构造函数和析构函数都是nops,那么编译器可以跳过堆栈分配,但是你为什么要关心呢? @ChrisSubagio:我猜注册/发送计时器是通过函数调用完成的。如果编译器不知道这些函数,它必须假设它们具有可观察的行为,并且如果编译器知道这些函数,它就知道它们具有。 【参考方案1】:

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

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

例如来自最新标准的第 1.9 节(有更多相关引用):

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

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

背景

让编译器优化掉琐碎的大小写构造函数非常很有帮助。它允许迭代器编译成与使用原始指针/索引器完全相同的性能代码。

这也是允许函数对象编译成与内联函数体完全相同的代码的原因。

这正是 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 虽然可能有更快的算法来获得阶乘,具体取决于问题领域:)

【讨论】:

在标准中添加了一些与可观察行为相关的参考 @LokiAstari:正是出于这个原因,我试图通过粗体单独knows更加突出! 【参考方案2】:

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

【讨论】:

整个程序优化,有人吗?编译器可以注释这样的构造函数,当代码静态链接时,链接器仍然可以省略对构造函数的冗余调用。现在,有了动态链接(共享对象),情况就不同了:共享对象的另一个版本可能会在构造函数中实现逻辑,并且它不能再被优化掉【参考方案3】:

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

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

确保编译器看不到构造函数和/或析构函数的定义。如果编译器不知道构造函数/析构函数的作用,它就不能假设它没有效果。但是请注意,这也会禁用内联。如果您的编译器不进行跨模块优化,只需将构造函数/析构函数放入不同的文件即可。

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

不过,让我再次强调,您不太可能必须做任何事情,除非您的编译器存在可怕的错误(在这种情况下,我强烈建议您更改编译器 :-))。

【讨论】:

以上是关于C++:调用临时对象的构造函数的主要内容,如果未能解决你的问题,请参考以下文章

C++学习:4拷贝友元

C++中派生类的构造函数怎么显式调用基类构造函数?

传递给构造函数的临时对象的 C++ 寿命

C++ 对象创建和构造函数

深入探讨C++中临时对象的常见产生情况及其解决的方案

C++对象模型