【问题标题】:When Does Move Constructor get called?何时调用移动构造函数?
【发布时间】:2012-10-19 00:05:31
【问题描述】:

我对何时调用移动构造函数和复制构造函数感到困惑。 我已阅读以下资料:

Move constructor is not getting called in C++0x

Move semantics and rvalue references in C++11

msdn

所有这些来源要么过于复杂(我只想要一个简单的例子),要么只展示如何编写移动构造函数,而不是如何调用它。我写了一个更具体的简单问题:

const class noConstruct{}NoConstruct;
class a
{
private:
    int *Array;
public:
    a();
    a(noConstruct);
    a(const a&);
    a& operator=(const a&);
    a(a&&);
    a& operator=(a&&);
    ~a();
};

a::a()
{
    Array=new int[5]{1,2,3,4,5};
}
a::a(noConstruct Parameter)
{
    Array=nullptr;
}
a::a(const a& Old): Array(Old.Array)
{

}
a& a::operator=(const a&Old)
{
    delete[] Array;
    Array=new int[5];
    for (int i=0;i!=5;i++)
    {
        Array[i]=Old.Array[i];
    }
    return *this;
}
a::a(a&&Old)
{
    Array=Old.Array;
    Old.Array=nullptr;
}
a& a::operator=(a&&Old)
{
    Array=Old.Array;
    Old.Array=nullptr;
    return *this;
}
a::~a()
{
    delete[] Array;
}

int main()
{
    a A(NoConstruct),B(NoConstruct),C;
    A=C;
    B=C;
}

目前 A、B 和 C 都有不同的指针值。我希望 A 有一个新指针,B 有 C 的旧指针,C 有一个空指针。

有点离题,但如果有人可以建议我可以详细了解这些新功能的文档,我将不胜感激,并且可能不需要问更多问题。

【问题讨论】:

标签: c++ c++11 move-constructor


【解决方案1】:

调用移动构造函数:

  • 当一个对象初始化器是std::move(something)
  • 当对象初始值设定项为 std::forward<T>(something)T 不是左值引用类型时(用于“完美转发”的模板编程)
  • 当对象初始化器是临时的并且编译器没有完全消除复制/移动时
  • 当按值返回函数局部类对象并且编译器没有完全消除复制/移动时
  • 当抛出一个函数局部类对象并且编译器没有完全消除复制/移动时

这不是一个完整的列表。请注意,如果参数具有类类型(不是引用),“对象初始化器”可以是函数参数。

a RetByValue() {
    a obj;
    return obj; // Might call move ctor, or no ctor.
}

void TakeByValue(a);

int main() {
    a a1;
    a a2 = a1; // copy ctor
    a a3 = std::move(a1); // move ctor

    TakeByValue(std::move(a2)); // Might call move ctor, or no ctor.

    a a4 = RetByValue(); // Might call move ctor, or no ctor.

    a1 = RetByValue(); // Calls move assignment, a::operator=(a&&)
}

【讨论】:

  • a a4 = RetByValue(); // Might call move ctor, or no ctor. //
  • @artm 是的,这取决于编译器。实际上,C++17 稍微改变了规则,因此一些额外的情况可以保证更少的构造函数和析构函数调用。
  • “是的,这取决于编译器”......这就是让一些人疯狂的原因! :-) 就我而言,当我尝试删除默认移动构造函数时出现错误(我的意思是:“class_name (class_name &&) = delete;”)。错误是: "error: use of deleted function 'class_name::class_name(class_name&&)" 。代码错误行是“class_name test = class_name("test");”。然后我尝试定义“我的自定义移动构造函数”,它看起来只有在我使用“std::move”时才会被调用。这也取决于编译器吗?提前致谢! P.s.:我为冗长的评论道歉......
  • @bitfox 编译器无法选择代码是否正确,只有在某些情况下,如果代码有效,实际会发生什么。当 class_name 具有显式删除的移动构造函数时,语句 class_name test = class_name("test"); 在 C++11 和 C++14 中格式错误,但在 C++17 中格式正确,因为不涉及移动或复制构造函数。我认为您无法检测到 std::move(e) 表达式和其他右值表达式之间的区别。
  • @aschepler 感谢您的回复。您对 C++ 版本和“格式错误”的建议,促使我对一些观点进行排序。这个howardhinnant.github.io/classdecl.htmlstackoverflow.com/questions/37092864 让我明白了很多东西。
【解决方案2】:

首先,您的复制构造函数已损坏。复制自和复制到对象都将指向同一个Array,并且当它们超出范围时都会尝试delete[],从而导致未定义的行为。要修复它,请复制数组。

a::a(const a& Old): Array(new int[5])
{
  for( size_t i = 0; i < 5; ++i ) {
    Array[i] = Old.Array[i];
  }
}

现在,移动赋值并没有按照您的意愿执行,因为两个赋值语句都是从左值进行赋值,而不是使用右值。要执行移动,您必须从右值开始移动,或者它必须是可以将左值视为右值的上下文(例如函数的 return 语句)。

要获得所需的效果,请使用std::move 创建一个右值引用。

A=C;              // A will now contain a copy of C
B=std::move(C);   // Calls the move assignment operator

【讨论】:

  • B=std::move(C); // Calls the copy assignment operator 不应该是移动赋值运算符吗?之后,C 可能不再是以前的样子了。
【解决方案3】:

请记住,可能会发生复制省略。如果您通过将-fno-elide-constructors 标志传递给编译器来禁用它,您的构造函数可能会被执行。

你可以在这里阅读:https://www.geeksforgeeks.org/copy-elision-in-c/

【讨论】:

    猜你喜欢
    • 2011-05-22
    • 1970-01-01
    • 2017-11-03
    • 2015-06-27
    • 2015-02-21
    • 1970-01-01
    • 2016-03-28
    • 2019-05-27
    • 1970-01-01
    相关资源
    最近更新 更多