我阅读了以下关于右值参考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
所以这些是我的问题:
答案 0 :(得分:2)
当构造函数2和3注释时,它试图调用构造函数2.为什么?
因为你的声明从临时对象初始化returnValue
- 临时需要是可移动的或可复制的,使用移动或复制构造函数。当你注释掉它们,并通过声明一个移动赋值运算符来禁止它们的隐式生成时,它们就不可用,所以不允许初始化。
应该省略实际的移动或复制,这就是为什么你只看到&#34;默认构造函数&#34;当你取消注释它们时。但即使被淘汰,也必须有适当的构造函数。
为什么会隐式删除我的普通拷贝构造函数?
通常,如果您的类具有时髦的移动语义,那么默认的复制语义将是错误的。例如,它可能会复制指向一个对象的指针,该对象只能由您的类的单个实例指向;这可能反过来导致双重删除或其他错误。 (事实上,你的移动构造函数就是这样做的,因为你忘了取消参数的指针)。
删除复制功能更安全,如果需要,可以让你正确实现它们,而不是生成几乎肯定会导致错误的功能。
如果是这样,为什么它允许我去#le;取消删除&#34;它通过明确定义并使用它?
因为您经常希望实现复制语义以及移动语义。
请注意,调用3 a&#34;移动构造函数&#34;更常规。和5 a&#34;移动赋值运算符&#34;,因为它们移动而不是复制它们的参数。
答案 1 :(得分:2)
因为您要删除复制构造函数和行
auto returnValue = PointerHolder<int>(new int(i));
不是真正的赋值,它调用复制构造函数来构建对象。需要提供两个复制构造函数之一(通过引用或通过rvalue)才能成功初始化该临时对象。如果你对这两者都发表评论,那就没有运气了。
如果一切都可用,会发生什么?为什么不叫那些?
这是一种名为“copy elision”的机制,基本上当一切都可以通过复制构造函数“初始化”returnValue时,编译器是一个聪明的人并意识到:
“哦,我可以像这样初始化returnValue”
PointerHolder<int> returnValue(new int(i));
这正是当一切都可用时会发生的事情。
至于为什么移动构造函数似乎克服了隐式的复制构造函数,我找不到比这更好的解释:https://stackoverflow.com/a/11255258/1938163
答案 2 :(得分:0)
您需要复制或移动构造函数来构建返回值。
如果你摆脱了所有复制/移动构造函数和所有赋值运算符(使用默认生成的构造函数/运算符),代码将被编译,但由于成员ptr
的多次删除而失败。
如果保留复制/移动构造函数和赋值运算符,则由于复制省略(返回值优化),您可能看不到任何构造函数的调用。
如果禁用copy elision(g ++: - fno-elide-constructors),由于成员ptr
的多次删除,代码将再次失败。
如果你更正了move-constructor:
// 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)