在下面的示例中,如果我们忽略互斥锁一秒钟,复制省略可以消除对复制构造函数的两次调用.
user_type foo()
{
unique_lock lock( global_mutex );
return user_type(...);
}
user_type result = foo();
Run Code Online (Sandbox Code Playgroud)
现在复制省略的规则没有提到线程,但我想知道它是否应该实际跨越这些边界.在上面的情况中,逻辑抽象机器内部线程中的最终副本发生在互斥锁释放后.但是,如果省略了副本,则结果数据结构在互斥锁中初始化,因此它在互斥锁释放之前发生.
我还没有想到一个具体的例子,复制省略如何真正导致竞争条件,但是对记忆序列的干扰似乎可能是个问题.任何人都可以明确地说它不会导致问题,或者有人可以产生一个确实可以破坏的例子吗?
为了确保答案不只是解决一个特殊情况,请注意,如果我有一个类似的声明,复制省略(根据我的阅读)仍然允许发生new (&result)( foo() ).也就是说,result不需要是堆栈对象.user_type本身也可以使用线程之间共享的数据.
答:我选择了第一个答案作为最相关的讨论.基本上,因为标准说elision可以发生,程序员只要在跨同步边界发生时就要小心.没有迹象表明这是有意还是无意的要求.我们仍然缺乏任何可能出错的例子,所以也许这不是问题.
线程与它无关,但锁的构造函数/析构函数的顺序可能会影响你.
查看代码执行的低级步骤,逐个复制省略(使用GCC选项-fno-elide-constructors):
lock.user_type使用(...)参数构造临时.user_type使用第2步中的值复制构造函数的临时返回值.lock.user_type result使用步骤3中的值复制构造.result.当然,通过多重复制省略优化,它将只是:
lock.result直接构造对象(...).lock.result.请注意,在这两种情况下,user_type构造函数(...)都受锁的保护.任何其他复制构造函数或析构函数调用可能不受保护.
事后补充:
我认为它最容易引起问题的地方是析构函数.也就是说,如果您构造的原始对象(...)处理任何共享资源的方式与其副本不同,并且在析构函数中执行需要锁定的操作,那么您就会遇到问题.
当然,这意味着您的对象首先设计得很糟糕,因为副本的行为与原始对象不同.
参考:
在C++ 11草案中,12.8.31(没有所有"动作"的类似措辞在C++ 98中:
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.没有优化就被破坏了.复制/移动操作的省略,称为复制省略,在以下情况下允许(可以合并以消除多个副本):
在具有类返回类型的函数的return语句中,当表达式是具有与函数返回类型相同的cvunqualified类型的非易失性自动对象(函数或catch子句参数除外)的名称时,副本通过将自动对象直接构造到函数的返回值中,可以省略/ move操作
函数或catch子句参数)其作用域不超出最内层封闭try-block的末尾(如果有的话),通过构造自动对象可以省略从操作数到异常对象的复制/移动操作直接进入异常对象
当一个未绑定到引用的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到省略的目标中来省略复制/移动操作复制/移动
当异常处理程序的异常声明声明一个相同类型的对象(cv-qualification除外)作为异常对象时,通过将异常声明视为异常对象的别名,可以省略复制/移动操作除了为exception-declaration声明的对象执行构造函数和析构函数之外,程序的含义将保持不变.
第1点和第3点在您的示例中进行协作以忽略所有副本.
| 归档时间: |
|
| 查看次数: |
249 次 |
| 最近记录: |