Noexcept并复制,移动构造函数 [英] Noexcept and copy, move constructors

查看:115
本文介绍了Noexcept并复制,移动构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在所有地方,我似乎都同意,当move构造函数为noexcept(false)时,标准库必须调用复制构造函数而不是move构造函数.

Everywhere I look it seems to be the agreement that the standard library must call copy constructors instead of move constructors when the move constructor is noexcept(false).

现在我不明白为什么会这样.而且,更多的Visual Studio VC v140和gcc v 4.9.2似乎有不同的用法.

Now I do not understand why this is the case. And futher more Visual Studio VC v140 and gcc v 4.9.2 seems to do this differently.

我不明白为什么除此以外的其他问题,例如向量.我的意思是,如果T不这样做,那么vector :: resize()应该如何能够给出强有力的异常保证.如我所见,向量的异常级别将取决于T.无论是否使用复制或移动. 我知道,除了对编译器进行异常处理优化外,它完全是眨眼.

I do not understand why noexcept this is a concern of e.g. vector. I mean how should vector::resize() be able to give strong exception guarantee if T does not. As I see it the exception level of vector will be dependend on T. Regardless if copy or move is used. I understand noexcept to solely be a wink to the compiler to do exception handling optimization.

这个小程序在使用gcc编译时将调用复制构造函数,而在使用Visual Studio编译时将移动移动构造器.

This little program calls the copy constructor when compiled with gcc and move constructor when compiled with Visual Studio.

include <iostream>
#include <vector>

struct foo {
  foo() {}
  //    foo( const foo & ) noexcept { std::cout << "copy\n"; }
  //    foo( foo && ) noexcept { std::cout << "move\n"; }
  foo( const foo & )  { std::cout << "copy\n"; }
  foo( foo && )  { std::cout << "move\n"; }

  ~foo() noexcept {}
};

int main() {
    std::vector< foo > v;
    for ( int i = 0; i < 3; ++i ) v.emplace_back();
}

推荐答案

这是一个多方面的问题,因此在学习各个方面时,请多多包涵.

This is a multi-faceted question, so bear with me while I go through the various aspects.

标准库希望所有用户类型始终提供基本的异常保证.这种保证表示,当引发异常时,所涉及的对象仍处于有效(如果未知)状态,没有资源泄漏,没有违反基本语言不变式,并且在远处也没有发生任何怪异的动作(最后一个不是正式定义的一部分,但这实际上是一个隐含的假设.

The standard library expects all user types to always give the basic exception guarantee. This guarantee says that when an exception is thrown, the involved objects are still in a valid, if unknown, state, that no resources are leaked, that no fundamental language invariants are violated, and that no spooky action at a distance happened (that last one isn't part of the formal definition, but it is an implicit assumption actually made).

考虑Foo类的副本构造函数:

Consider a copy constructor for a class Foo:

Foo(const Foo& o);

如果此构造函数抛出异常,则基本的异常保证为您提供以下知识:

If this constructor throws, the basic exception guarantee gives you the following knowledge:

  • 未创建新对象.如果构造函数抛出该对象,则不会创建该对象.
  • o未修改.它唯一涉及的是通过const引用,因此不得对其进行修改.其他情况属于遥距动作",或者可能是基本语言不变性".
  • 没有资源泄漏,整个程序仍保持一致.
  • No new object was created. If a constructor throws, the object is not created.
  • o was not modified. Its only involvement here is via a const reference, so it must not be modified. Other cases fall under the "spooky action at a distance" heading, or possibly "fundamental language invariant".
  • No resources were leaked, the program as a whole is still coherent.

在移动构造函数中:

Foo(Foo&& o);

基本保证提供的保证较少.可以修改o,因为它是通过非常量引用涉及的,因此它可以处于任何状态.

the basic guarantee gives less assurance. o can be modified, because it is involved via a non-const reference, so it may be in any state.

接下来,查看vector::resize.其实现通常将遵循相同的方案:

Next, look at vector::resize. Its implementation will generally follow the same scheme:

void vector<T, A>::resize(std::size_t newSize) {
  if (newSize == size()) return;
  if (newSize < size()) makeSmaller(newSize);
  else if (newSize <= capacity()) makeBiggerSimple(newSize);
  else makeBiggerComplicated(newSize);
}
void vector<T, A>::makeBiggerComplicated(std::size_t newSize) {
  auto newMemory = allocateNewMemory(newSize);
  constructAdditionalElements(newMemory, size(), newSize);
  transferExistingElements(newMemory);
  replaceInternalBuffer(newMemory, newSize);
}

此处的关键功能是transferExistingElements.如果我们仅使用复制,则它有一个简单的保证:它不能修改源缓冲区.因此,如果在任何时候抛出操作,我们都可以销毁新创建的对象(请记住,标准库绝对不能与抛出析构函数一起使用),丢弃新缓冲区并重新抛出.向量看起来好像从未修改过.这意味着即使元素的copy构造函数仅提供弱保证,我们也具有强保证.

The key function here is transferExistingElements. If we only use copying, it has a simple guarantee: it cannot modify the source buffer. So if at any point an operation throws, we can just destroy the newly created objects (keep in mind that the standard library absolutely cannot work with throwing destructors), throw away the new buffer, and rethrow. The vector will look as if it had never been modified. This means we have the strong guarantee, even though the element's copy constructor only offers the weak guarantee.

但是,如果我们改用移动,则此操作将无效.从一个对象移出后,任何后续异常都意味着源缓冲区已更改.而且由于我们不能保证也不会向后移动对象,因此我们甚至无法恢复.因此,为了保持有力的保证,我们必须要求move操作不要抛出任何异常.如果有的话,我们很好.这就是为什么我们有move_if_noexcept.

But if we use moving instead, this doesn't work. Once one object is moved from, any subsequent exception means that the source buffer has changed. And because we have no guarantee that moving objects back doesn't throw too, we cannot even recover. Thus, in order to keep the strong guarantee, we have to demand that the move operation doesn't throw any exceptions. If we have that, we're fine. And that's why we have move_if_noexcept.

关于MSVC和GCC之间的区别:自版本14起,MSVC仅支持noexcept,由于该版本仍在开发中,我怀疑标准库尚未更新以利用.

As to the difference between MSVC and GCC: MSVC only supports noexcept since version 14, and since that is still in development, I suspect the standard library hasn't been updated to take advantage yet.

这篇关于Noexcept并复制,移动构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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