[英]Rvalue references and constructors
我阅读了以下有关右值参考的文章http://thbecker.net/articles/rvalue_references/section_01.html
但是有些事情我不明白。
这是我使用的代码:
#include <iostream>
template <typename T>
class PointerHolder
{
public:
// 1
explicit PointerHolder(T* t) : ptr(t)
{
std::cout << "default constructor" << std::endl;
}
// 2
PointerHolder(const PointerHolder& lhs) : ptr(new T(*(lhs.ptr)))
{
std::cout << "copy constructor (lvalue reference)" << std::endl;
}
// 3
PointerHolder(PointerHolder&& rhs) : ptr(rhs.ptr)
{
rhs.ptr = nullptr;
std::cout << "copy constructor (rvalue reference)" << std::endl;
}
// 4
PointerHolder& operator=(const PointerHolder& lhs)
{
std::cout << "copy operator (lvalue reference)" << std::endl;
delete ptr;
ptr = new T(*(lhs.ptr));
return *this;
}
// 5
PointerHolder& operator=(PointerHolder&& rhs)
{
std::cout << "copy operator (rvalue reference)" << std::endl;
std::swap(ptr, rhs.ptr);
return *this;
}
~PointerHolder()
{
delete ptr;
}
private:
T* ptr;
};
PointerHolder<int> getIntPtrHolder(int i)
{
auto returnValue = PointerHolder<int>(new int(i));
return returnValue;
}
如果我评论构造函数2和3,编译器会说:
error: use of deleted function ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’
auto returnValue = PointerHolder<int>(new int(i));
^
../src/rvalue-references/move.cpp:4:7: note: ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’ is implicitly declared as deleted because ‘PointerHolder<int>’ declares a move constructor or move assignment operator
但是,如果我取消注释两者中的任何一个,它将编译并执行,结果如下:
default constructor
所以这是我的问题:
当构造函数2和3发表评论时,它试图调用构造函数2。为什么?
因为您的声明会初始化临时对象的returnValue
,所以该临时对象需要使用move或copy构造函数进行移动或复制。 当您注释掉这些内容并通过声明移动分配运算符禁止它们隐式生成时,它们将不可用,因此不允许进行初始化。
应当忽略实际的移动或复制,这就是为什么在取消注释时只看到“默认构造函数”的原因。 但是即使被删除,也必须有适当的构造函数。
为什么它隐式删除我的普通副本构造函数?
通常,如果您的类具有时髦的move语义,则默认的复制语义将是错误的。 例如,它可能将一个指针复制到一个对象,该对象仅应由类的单个实例指向; 进而可能导致重复删除或其他错误。 (实际上,您的move构造函数正是这样做的,因为您忘记了使参数的指针无效)。
删除复制功能,并让您在需要时正确实现它们,比生成几乎肯定会导致错误的功能更安全。
如果是这样,为什么它允许我通过显式定义和使用它“取消删除”它?
因为您经常想实现复制语义以及移动语义。
请注意,将3称为“移动构造函数”,将5称为“移动赋值运算符”是更常规的做法,因为它们移动而不是复制其参数。
因为您要删除复制构造函数和行
auto returnValue = PointerHolder<int>(new int(i));
不是真正的任务,它会调用复制构造函数来构建对象。 需要两个副本构造函数之一(通过引用或通过rvalue)才能成功从该临时实例初始化对象。 如果您将两者都注释掉,那么这样做是没有运气的。
如果一切可用,会发生什么? 为什么不给他们打电话?
这是一种称为“ 复制省略 ”的机制,基本上,到所有东西都可以使用复制构造函数“初始化” returnValue时,编译器才是聪明的家伙,并且意识到:
“哦,我可以像这样初始化returnValue”
PointerHolder<int> returnValue(new int(i));
当一切都可用时,这正是发生的情况。
至于为什么move构造函数似乎克服了隐式copy-constructor,我找不到比这更好的解释: https : //stackoverflow.com/a/11255258/1938163
您需要一个复制或移动构造函数来构造您的返回值。
如果您摆脱了所有复制/移动构造函数和所有赋值运算符(使用默认生成的构造函数/运算符),则代码将编译,但由于成员ptr
多次删除而不幸失败。
如果保留复制/移动构造函数和赋值运算符,由于复制省略(返回值优化),您可能看不到构造函数的任何调用。
如果禁用复制省略(g ++:-fno-elide-constructors),则由于成员ptr
多次删除,代码将再次失败。
如果更正了移动构造函数:
// 3
PointerHolder(PointerHolder&& rhs) : ptr(0)
{
std::swap(ptr, rhs.ptr);
std::cout << "copy constructor (rvalue reference)" << std::endl;
}
并使用禁用的复制省略进行编译,结果可能是:
default constructor
copy constructor (rvalue reference)
copy constructor (rvalue reference)
copy constructor (rvalue reference)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.