可以复制精度发生跨同步与语句? [英] Can copy elision happen across synchronize-with statements?

查看:117
本文介绍了可以复制精度发生跨同步与语句?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的示例中,如果我们忽略互斥体一秒,复制elision可以消除对复制构造函数的两个调用。

  user_type foo()
{
unique_lock lock(global_mutex);
return user_type(...);
}

user_type result = foo();

现在复制elision的规则没有提到线程,但我想知道它是否应该发生在这样的边界。在上面的情况下,最终的拷贝,在逻辑抽象机之间的线程发生在互斥体释放之后。然而,如果复制被省略,结果数据结构在互斥体内被初始化,因此它在线程发生在互斥体被释放之前。



我还没有想到一个具体的例子如何复制精确度可以真正导致竞争条件,但在内存序列的干扰似乎可能是问题。任何人都可以肯定地说它不会导致问题,或者是否有人能够产生一个确实可以打破的例子?






答案不只是解决一个特殊情况,请注意,如果我有一个语句如 new(& result)(foo()),复制elision仍然允许发生/ code>。也就是说, result 不需要是一个堆栈对象。 user_type 本身也可以处理线程之间共享的数据。






答案:我选择了第一个答案作为最相关的讨论。基本上,由于标准表示可能发生错误,程序员只是在发生跨同步边界时要小心。没有迹象表明这是一个有意还是偶然的要求。

解决方案

我们仍然缺少任何可能出错的例子,线程与它无关,但锁的构造函数/析构函数的顺序可能会影响你。



看看你的代码做的低级步骤, (使用GCC选项-fno-elide-constructors):


  1. 构造 lock

  2. 使用(...) user_type $ c>参数。

  3. 使用第2步中的值复制 - c>使用 user_type 类型的函数的临时返回值。

  4. 销毁第2步中的临时邮件。

  5. 销毁。 b $ b
  6. 使用第3步中的值复制 user_type result

  7. 从步骤3中销毁临时文件。 / li>
  8. 稍后,销毁结果

自然地,对于多个复制精度优化,它将只是:


  1. 构造

  2. 使用(...)直接构造结果 c>。

  3. 销毁锁。

  4. > result

请注意,在这两种情况下, user_type 构造函数(...)受锁保护。任何其他复制构造函数或析构函数调用可能不受保护。



预想后



我认为最可能的地方,它可以导致问题是在析构函数。也就是说,如果你的原始对象,用(...)构造的处理任何共享资源不同于它的副本,并在析构函数中需要锁,你有一个问题。



当然,这意味着你的对象在设计上是不好的,因为副本不像原来的对象。



参考



在C ++ 11草稿中,12.8.31没有所有的移动是在C ++ 98:


当满足某些标准时,允许实现省略复制/移动类
对象的构造,即使对于对象的复制/移动构造函数和/或析构函数有副作用。在这种情况下,
实现处理省略复制/移动的源和目标操作作为简单的两个不同的
方法来引用同一个对象,并且该对象的销毁发生在时间
的后面,当两个对象在没有优化的情况下被销毁。在以下情况下允许复制/移动
操作(称为复制精确):




  • 在具有类返回类型的函数中的返回语句中,当表达式是
    非易失性自动对象的名称(除函数或catch-子句参数)与函数返回类型具有相同的cvunqualified
    类型,通过构造
    可以省略复制/移动操作自动对象直接到函数的返回值


  • 一个函数或catch子句参数),其范围不会超出包含try-block的最里面的
    的末尾(如果有一个),则从当没有绑定的临时类对象时,通过将自动对象直接构造为异常对象


  • ,可以忽略
    对象的操作数到一个引用将被复制/移动
    到具有相同cv非限定类型的类对象,复制/移动操作可以通过
    省略直接构造临时对象到省略的复制/当异常处理程序的异常声明声明一个类型为
    (cv-qualification除外)的对象作为异常对象时,移动


  • < ,如果程序
    的含义不变,除了执行由$ b声明的对象的构造函数和析构函数,则可以通过将异常声明作为异常对象的别名来处理来省略复制/移动操作$ b例外声明。



以清除所有副本。


In the example below, if we ignore the mutex for a second, copy elision may eliminate the two calls to the copy constructor.

user_type foo()
{
  unique_lock lock( global_mutex );
  return user_type(...);
}

user_type result = foo();

Now the rules for copy elision don't mention threading, but I'm wondering whether it should actually happen across such boundaries. In the situation above, the final copy, in the logical abstract machine inter-thread happens after the mutex is released. If however the copies are omitted the result data structure is initialized within the mutex, thus it inter-thread happens before the mutex is released.

I have yet to think of a concrete example how copy elision could truly result in a race condition, but the interference in the memory sequence seems like it might be problem. Can anybody definitively say it can not cause a problem, or can somebody produce an example that can indeed break?


To ensure the answer doesn't just address a special case, note that copy elision is (according to my reading) still allowed to occur if I have a statement like new (&result)( foo() ). That is, result does not need to be a stack object. user_type itself may also work with data shared between threads.


Answer: I've chosen the first answer as the most relevant discussion. Basically since the standard says elision can happen, the programmer just has to be careful when it happens across synchronization bounds. There is no indication of whether this is an intentional or accidental requirement. We're still lacking in any example showing what could go wrong, so perhaps it isn't an issue either way.

解决方案

Threads have nothing to do with it, but the order of constructors/destructors of the lock may affect you.

Looking at the low level steps your code does, with out copy elision, one by one (using the GCC option -fno-elide-constructors):

  1. Construct lock.
  2. Construct the temporary user_type with (...) arguments.
  3. Copy-construct the temporary return value of the function, of type user_type using the value from step 2.
  4. Destroy the temporary from step 2.
  5. Destroy lock.
  6. Copy construct the user_type result using the value from step 3.
  7. Destroy the temporary from step 3.
  8. Later on, destroy result.

Naturally, with the multiple copy elision optimizations, it will be just:

  1. Construct lock.
  2. Construct the result object directly with (...).
  3. Destroy lock.
  4. Later on, destroy result.

Note that in both cases the user_type constructor with (...) is protected by the lock. Any other copy constructor or destructor call may not be protected.

Afterthoughts:

I think that the most likely place where it can cause problems is in the destructors. That is, if your original object, that constructed with (...) handles any shared resource differently than its copies, and does something in the destructor that needs the lock, then you have a problem.

Naturally, that would mean that your object is badly design in the first place, as copies do not behave as the original object.

Reference:

In the C++11 draft, 12.8.31 (a similar wording without all the "moves" is in C++98:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cvunqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

  • a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object can be omitted by constructing the automatic object directly into the exception object

  • when a temporary class object that has not been bound to a reference would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

  • when the exception-declaration of an exception handler declares an object of the same type (except for cv-qualification) as the exception object, the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.

Points 1 and 3 collaborate in your example to elide all the copies.

这篇关于可以复制精度发生跨同步与语句?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆