use*_*460 11 c++ noexcept c++11
在我看来,似乎是协议,当移动构造函数为noexcept(false)时,标准库必须调用复制构造函数而不是移动构造函数.
现在我不明白为什么会这样.此外,Visual Studio VC v140和gcc v 4.9.2似乎也有不同的做法.
我不明白为什么除了这是例如矢量的关注.我的意思是如果T没有,vector :: resize()应该如何能够提供强大的异常保证.正如我所看到的那样,矢量的异常级别将取决于T.无论是否使用复制或移动.我理解noexcept只是对编译器进行异常处理优化的眨眼.
当使用Visual Studio编译时,这个小程序在使用gcc编译时调用复制构造函数并移动构造函数.
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();
}
Run Code Online (Sandbox Code Playgroud)
Seb*_*edl 25
这是一个多方面的问题,所以在我经历各个方面的时候请耐心等待.
标准库期望所有用户类型始终提供基本异常保证.这种保证表明,当抛出异常时,所涉及的对象仍处于有效(如果未知)状态,没有资源泄露,没有违反基本语言不变量,并且没有发生远距离的怪异动作(最后一次)一个不是正式定义的一部分,但它实际上是一个隐含的假设).
考虑类Foo的复制构造函数:
Foo(const Foo& o);
Run Code Online (Sandbox Code Playgroud)
如果此构造函数抛出,基本异常保证将为您提供以下知识:
o
没有修改.它唯一的参与是通过const引用,所以不能修改它.其他案件属于"远距离的怪异行动"标题,或者可能是"基本语言不变".在移动构造函数中:
Foo(Foo&& o);
Run Code Online (Sandbox Code Playgroud)
基本保证给予较少的保证.o
可以修改,因为它通过非const引用涉及,因此它可以处于任何状态.
接下来,看看vector::resize
.它的实施通常遵循相同的方案:
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);
}
Run Code Online (Sandbox Code Playgroud)
这里的关键功能是transferExistingElements
.如果我们只使用复制,它有一个简单的保证:它不能修改源缓冲区.因此,如果操作抛出的任何时刻,我们可以破坏新创建的对象(请记住标准库绝对不能用于抛出析构函数),抛弃新缓冲区,然后重新抛出.矢量看起来好像从未被修改过.这意味着我们有强有力的保证,即使元素的复制构造函数只提供弱保证.
但是,如果我们使用移动,这不起作用.移动一个对象后,任何后续异常都意味着源缓冲区已更改.而且因为我们无法保证移动物体也不会抛出,我们甚至无法恢复.因此,为了保持强有力的保证,我们必须要求移动操作不会抛出任何异常.如果我们有,那我们没事.这就是我们拥有的原因move_if_noexcept
.
至于MSVC和GCC之间的区别:MSVC仅支持noexcept
版本14,因为它仍在开发中,我怀疑标准库尚未更新以便利用.
Pup*_*ppy 11
核心问题是使用投掷移动构造函数不可能提供强大的异常安全性.想象一下,如果在向量调整大小时,将元素移动到新缓冲区的中途,移动构造函数会抛出.你怎么可能恢复以前的状态?你不能再使用移动构造函数,因为,这可能只是继续投掷.
复制适用于强大的异常安全保证,因为原始状态没有被损坏,因此如果你不能构建整个新状态,你可以只清理部分构建的状态,然后你就完成了,因为旧州还在这里等着你.移动构造函数不提供此安全网.
基本上不可能通过抛出移动提供强烈的异常安全resize(),但是使用抛出副本很容易.这个基本事实反映在标准库的各个角落.
GCC和VS对此有不同的看法,因为它们处于不同的一致性阶段.VS已经noexcept
成为他们实现的最后一个功能之一,因此它们的行为是C++ 03行为与C++ 11/14之间的中间行为.特别是,由于他们无法判断你的移动构造函数是否真实noexcept
,所以他们基本上只需要猜测.从内存中他们只是假设这是noexcept
因为投掷移动构造函数并不常见且无法移动将是一个关键问题.
归档时间: |
|
查看次数: |
3851 次 |
最近记录: |