C++11 C++;为什么在Move构造函数和Move赋值操作符的上下文中需要noexcept来启用优化?

C++11 C++;为什么在Move构造函数和Move赋值操作符的上下文中需要noexcept来启用优化?,c++11,move-semantics,noexcept,C++11,Move Semantics,Noexcept,考虑以下具有移动构造函数和移动赋值运算符的类: class my_class { protected: double *my_data; uint64_t my_data_length; } my_class(my_class&& other) noexcept : my_data_length{other.my_data_length}, my_data{other.my_data} { // Steal the data othe

考虑以下具有移动构造函数和移动赋值运算符的类:

class my_class
{

    protected:

    double *my_data;
    uint64_t my_data_length;
}

my_class(my_class&& other) noexcept : my_data_length{other.my_data_length}, my_data{other.my_data}
{
    // Steal the data
    other.my_data = nullptr;
    other.my_data_length = 0;
}

const my_class& operator=(my_class&& other) noexcept
{
    // Steal the data
    std::swap(my_data_length, other.my_data_length);
    std::swap(my_data, other.my_data);

    return *this;
}

这里的
noexcept
的目的是什么?我知道,对于编译器来说,下面的函数不应该抛出异常,

IMHO使用
noexcept
本身不会启用任何编译器优化。STL有以下特点:

std::is_nothrow_move_constructible
std::is_nothrow_move_assignable
vector
etc这样的STL容器使用这些特性来测试T类型,并使用移动构造函数和赋值,而不是复制构造函数和赋值

为什么STL使用这些特性而不是:

std::is_move_constructible
std::is_move_assignable

答:提供强大的异常保证。

首先,我要指出的是,在移动构造函数或移动赋值中,任何东西都不应该抛出,而且似乎永远都不需要这样做。在构造函数/赋值运算符中必须做的唯一一件事是处理已经分配的内存和指向它们的指针。通常,您不应该调用任何其他可以抛出的方法,而您自己在构造函数/运算符中的移动也不需要这样做。但另一方面,调试消息的简单输出打破了这一规则

优化可以通过一些不同的方式进行。由编译器自动执行,也由使用构造函数和赋值运算符的不同代码实现自动执行。看看STL,对于代码有一些专门化,如果您使用异常或不使用异常,它们是不同的,通过类型特征实现的

编译器本身可以更好地优化,同时保证任何代码都不会抛出。编译器通过您的代码有一个有保证的调用树,它可以更好地进行内联、编译时计算等等。可以做的最小优化是不存储处理抛出条件所需的有关实际堆栈帧的所有信息,如堆栈上的释放变量和其他内容

这里还有一个问题:

也许你的问题是重复的

我在这里发现了一个可能有用的问题: 本文讨论了投掷动作操作的必要性

noexcept的目的是什么


至少节省一些程序空间,这不仅与移动操作有关,而且与所有功能有关。如果您的类与STL容器或算法一起使用,那么它可以处理不同的问题,如果您的STL实现使用这些信息,则可以实现更好的优化。如果所有其他内容都是编译时常量,那么编译器可能会因为一个已知的调用树而获得更好的通用优化。

移动构造函数和赋值运算符的特殊重要性将在

基本上,它不支持传统意义上的“优化”,即允许编译器生成更好的代码。相反,它允许其他类型(如库中的容器)在检测到移动元素类型永远不会抛出错误时采用不同的代码路径。这样就可以选择一个备用的代码路径,如果它们可能抛出该路径,则该路径将不安全(例如,因为这样会阻止容器相遇)


例如,当您对向量执行
push_back(t)
操作时,如果向量已满(
size()==capacity()
),则它需要分配一个新内存块,并将所有现有元素复制到新内存中。如果复制任何元素都会引发异常,那么库只会销毁它在新存储中创建的所有元素,并释放新内存,使原始向量保持不变(从而满足强大的异常安全保证)。将现有元素移动到新存储中会更快,但如果移动可以抛出,那么任何已移动的元素都将已经被更改,并且不可能满足强保证,因此库只会在知道不能抛出的情况下尝试移动它们,它只能知道它们是否是
noexcept

啊,所以我必须始终记住将
noexcept
添加到移动构造函数或移动赋值操作符中?是,例如,如果复制构造函数执行数据的深度复制,并且希望在STL容器中使用类,那么这一点很重要。是的,这并没有回答我的问题。。。从您在第一段中所说的,听起来好像编译器只是使用
noexcept
来检查函数的任何部分是否可以引发异常,如果为true,则会引发编译器错误?(对在成员函数声明末尾使用
const
很有帮助?在这种情况下,我们说“此函数不会修改任何成员数据”,如果修改了,则会出现编译器错误。)这是对第一段的正确解释吗?根本不会出现编译错误,但如果在运行时发生抛出,您的程序通常结束。因此,编译器知道调用树是什么样子的。如果发生抛出,这无关紧要,因为这个方法的执行永远不会因为程序结束而继续。但我的第一段只是问为什么任何人都应该做投掷动作。我认为根本没有必要这样做。好吧,所以基本上我的解释是不正确的,重要的是把
noexcept
放在那里?我添加了我的答案,希望它变得更清楚一点。是的,noexcept通常会在更少的空间消耗下生成更好的代码。@user3728501,“如果为true,则会引发编译器错误?”不,正如Klaus所说,在编译时不会检查异常规范。为什么它不能执行更好的优化?我的理解是,可以执行更好的内联。这是错误的吗?如果是,原因是什么?@Klaus,对于OP的示例,编译器已经可以看到那些操作没有抛出,添加
noexcept
不会告诉它什么。在g