Rvalue引用和构造函数
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。为什么?我希望它调用构造函数1,这就是我取消注释它们时它所做的
- 关于这个错误:我声明了一个"move"复制操作符,这意味着我的对象可以从右值引用中"移动"复制。但是为什么它会隐式地删除我的普通复制构造函数呢?如果是这样,为什么它允许我通过明确定义并使用它来"取消删除"它
当构造函数2和3被注释时,它尝试调用构造函数2。为什么?
因为您的声明从临时对象初始化returnValue
,所以该临时对象需要是可移动的或可复制的,使用move或copy构造函数。当您注释掉这些,并通过声明移动赋值运算符来禁止它们的隐式生成时,它们不可用,因此不允许初始化。
实际的移动或复制应该被忽略,这就是为什么在取消注释它们时只看到"默认构造函数"的原因。但是,即使取消了,也必须提供适当的构造函数。
为什么它会隐式删除我的普通复制构造函数?
通常,如果您的类具有时髦的移动语义,那么默认的复制语义将是错误的。例如,它可能会复制一个指向对象的指针,而该对象本应仅由类的单个实例指向;这又可能导致双重删除或其他错误。(事实上,您的move构造函数正是这样做的,因为您忘记了取消参数的指针)。
删除复制函数,并让您在需要时正确实现它们,比生成几乎肯定会导致错误的函数更安全。
如果是这样,为什么它允许我通过明确定义并使用它来"取消删除"它?
因为您通常想要实现复制语义和移动语义。
请注意,更传统的做法是将3称为"移动构造函数",将5称为"移赋值运算符",因为它们是移动的,而不是复制它们的参数
因为您正在删除复制构造函数和行
auto returnValue = PointerHolder<int>(new int(i));
不是真正的赋值,而是调用复制构造函数来构建对象。两个副本构造函数中的一个(通过引用或通过右值)需要可用,才能成功地从该临时构造函数初始化对象。如果你把这两个都评论掉,那就没有运气了。
如果一切都可用,会发生什么?为什么不叫这些?
这是一种称为"复制省略"的机制,基本上到所有东西都可以正确地使用复制构造函数"初始化"returnValue时,编译器是一个聪明的男孩,并实现:
"哦,我可以这样初始化returnValue"
PointerHolder<int> returnValue(new int(i));
当一切都可用时,就会发生这种情况。
至于为什么move构造函数似乎克服了隐式复制构造函数,我找不到比这更好的解释了:https://stackoverflow.com/a/11255258/1938163
您需要一个复制或移动构造函数来构造返回值。
如果去掉了所有复制/移动构造函数和所有赋值运算符(使用默认生成的构造函数/运算符),代码将进行编译,但由于多次删除成员ptr
而失败。
如果保留复制/移动构造函数和赋值运算符,由于复制省略(返回值优化),您可能看不到任何对构造函数的调用。
如果禁用复制省略(g++:-fno省略构造函数),则由于多次删除成员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)
- 在类构造函数中传递对外部函数的引用
- 在 c++ 中将变量作为结构构造函数中的引用传递
- C++ 尝试在不存在的构造函数中引用已删除的函数(使用 rapidJson)
- 移动构造函数和右值引用
- 为什么我的运算符 + 重载尽管是通过引用传递的,但仍调用我的复制构造函数?
- 如何使用 swig 修改类构造函数以保留对其中一个构造函数参数的引用?
- 在引用初始化中使用已删除的复制构造函数进行复制初始化
- 为什么我需要在转换构造函数上引用 this->?
- 关于隐式声明的复制构造函数的引用在逻辑上不清楚
- C++:右值引用构造函数和复制省略
- CLion - 无法解决类中对构造函数的未定义引用
- 当有右值构造函数可用时,为什么从右值调用类引用构造函数重载?
- 为什么定义复制构造函数会给我错误:无法将类型 'obj&' 的非常量左值引用绑定到类型为"obj"的右值?
- 调用默认构造函数时不引用它
- C++ 构造函数传递对基类对象的引用
- 使用默认构造函数引用成员变量初始化错误
- 构造函数引用参数导致seg错误
- 未定义的类构造函数引用,当它被正确定义时
- 如何从父类构造函数引用重写的静态方法
- 创建构造函数引用全局变量的类的全局实例时,C++ 程序崩溃