在下面的示例中,如果我们暂时忽略互斥锁,复制省略可能会消除对复制构造函数的两次调用。
user_type foo()
{
unique_lock lock( global_mutex );
return user_type(...);
}
user_type result = foo();
现在复制省略的规则没有提到线程,但我想知道它是否真的应该跨越这样的边界发生。在上面的情况下,逻辑抽象机线程间的最终副本发生在互斥体被释放之后。然而,如果省略副本,则结果数据结构将在互斥体中初始化,因此线程间发生在互斥体被释放之前。
我还没有想到一个具体的例子,复制省略如何真正导致竞争条件,但内存序列中的干扰似乎可能是个问题。任何人都可以明确地说它不会导致问题,或者有人可以提供一个确实可以破坏的示例吗?
为了确保答案不仅仅解决特殊情况,请注意,如果我有类似的声明,复制省略(根据我的阅读)仍然允许发生new (&result)( foo() )
。那是,result
不需要是堆栈对象。user_type
它本身也可以处理线程之间共享的数据。
Answer:我选择第一个答案作为最相关的讨论。基本上,由于标准规定可能会发生省略,因此程序员在发生跨越同步边界的情况时必须小心。没有迹象表明这是有意还是无意的要求。我们仍然缺乏任何示例来说明可能出现的问题,所以也许这都不是问题。
线程与它无关,但锁的构造函数/析构函数的顺序可能会影响你。
查看代码执行的低级步骤,不进行复制省略,一一查看(使用 GCC 选项 -fno-elide-constructors):
- 构造
lock
.
- 搭建临时
user_type
with (...)
论据。
- 复制构造函数的临时返回值,类型为
user_type
使用步骤 2 中的值。
- 销毁步骤 2 中的临时文件。
- Destroy
lock
.
- 复制构造
user_type result
使用步骤 3 中的值。
- 销毁步骤 3 中的临时文件。
- 后来毁了
result
.
当然,通过多重复制省略优化,它将只是:
- 构造
lock
.
- 构建
result
直接对象(...)
.
- Destroy
lock
.
- 后来毁了
result
.
请注意,在这两种情况下user_type
构造函数与(...)
受锁保护。任何其他复制构造函数或析构函数调用可能不受保护。
事后的想法:
我认为最有可能导致问题的地方是析构函数。也就是说,如果你的原始对象是用(...)
以不同于其副本的方式处理任何共享资源,并在析构函数中执行需要锁的操作,那么就会遇到问题。
当然,这意味着您的对象首先设计得很糟糕,因为副本的行为与原始对象不同。
参考:
在 C++11 草案中,12.8.31(C++98 中的类似措辞没有所有“移动”:
当满足某些条件时,允许实现省略类的复制/移动构造
对象,即使对象的复制/移动构造函数和/或析构函数有副作用。在这种情况下,
该实现将省略的复制/移动操作的源和目标视为两个不同的
引用同一对象的方式,并且该对象的销毁发生在较晚的时间
如果不进行优化,这两个对象就会被销毁。复制/移动的省略
称为复制省略的操作在以下情况下是允许的(可以组合起来)
消除多个副本):
在具有类返回类型的函数的 return 语句中,当表达式是 a 的名称时
具有相同 cvunqualified 的非易失性自动对象(函数或 catch 子句参数除外)
type 作为函数返回类型,可以通过构造省略复制/移动操作
自动对象直接进入函数的返回值
函数或 catch 子句参数),其范围不超出最内层的末尾
封闭 try 块(如果有),从操作数到异常的复制/移动操作
可以通过直接将自动对象构造到异常对象中来省略对象
当复制/移动尚未绑定到引用的临时类对象时
对于具有相同 cv-unqualified 类型的类对象,可以通过以下方式省略复制/移动操作
将临时对象直接构造到省略的复制/移动的目标中
当异常处理程序的异常声明声明相同类型的对象时
(除了 cv 限定)作为异常对象,如果程序的含义可以通过将异常声明视为异常对象的别名来省略复制/移动操作
除了执行声明的对象的构造函数和析构函数之外,将保持不变
异常声明。
第 1 点和第 3 点在您的示例中协作删除所有副本。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)