提问人:Maestro 提问时间:2/12/2020 最后编辑:Maestro 更新时间:2/12/2020 访问量:84
复制和交换习惯用语与移动操作之间的交互
Interaction between copy-and-swap idiom and move operations
问:
我正在尝试制作一个程序来实现“复制和交换”习语之间的交互,所以我编写了以下代码:move control operations
class PInt
{
public:
PInt(int = 0);
PInt(const PInt&);
PInt(PInt&&) noexcept;
PInt& operator=(PInt);
~PInt();
int* getPtr()const;
private:
int* ptr;
friend void swap(PInt&, PInt&);
};
PInt::PInt(int x) :
ptr(new int(x))
{
std::cout << "ctor\n";
}
PInt::PInt(const PInt& rhs) :
ptr(new int(rhs.ptr ? *rhs.ptr : 0))
{
std::cout << "copy-ctor\n";
}
PInt::PInt(PInt&& rhs) noexcept :
ptr(rhs.ptr)
{
std::cout << "move-ctor\n";
rhs.ptr = nullptr; // putting rhs in a valid state
}
PInt& PInt::operator=(PInt rhs)
{
std::cout << "copy-assignment operator\n";
swap(*this, rhs);
return *this;
}
PInt::~PInt()
{
std::cout << "dtor\n";
delete ptr;
}
void swap(PInt& lhs, PInt& rhs)
{
std::cout << "swap(PInt&, PInt&\n";
using std::swap;
swap(lhs.ptr, rhs.ptr);
}
PInt gen_PInt(int x)
{
return {x};
}
int main()
{
PInt pi1(1), pi2(2);
//pi1 = pi2; // 1
//pi1 = PInt{}; // 2
//pi1 = std::move(pi2); // 3
pi1 = std::move(PInt{}); // 4
}
对我来说一切都很好,所以我认为 copy-ctor是由 copy-asignment 运算符调用的,以初始化其参数(它按值获取),然后使用 swap。在“2”中,我是从 r 值分配的,因此我认为编译器应用了一些“复制省略”优化;在 copy-assignment 运算符中直接创建对象。
1
我不确定的是 3 和 4。所以这里是 3 和 4 的结果:
取消注释第 3 行:
ctor ctor move - ctor copy - assignment operator swap(PInt&, PInt & dtor dtor dtor
取消注释第 4 行:
ctor
ctor
ctor
move - ctor
copy - assignment operator
swap(PInt&, PInt &
dtor
dtor
dtor
dtor
- 为什么 3 和 4 使用但 for 调用了 n 个额外的构造函数?
std::move
** 哪个是有效的:定义一个按值或两个单独的版本获取的复制/移动分配运算符:复制分配和移动分配?因为每次调用的一个版本要么调用(额外调用)copy-ctor 要么调用 move-ctor 来初始化其参数?
答:
4赞
eerorika
2/12/2020
#1
为什么 3 和 4 使用 std::move,但 for 调用了 n 个额外的构造函数?
3 和 4 的“额外”(移动)构造函数是创建作为此处参数的对象:
PInt& PInt::operator=(PInt rhs)
^^^^^^^^
4 的“额外”构造函数是创建这个临时的:
pi1 = std::move(PInt{}); // 4
^^^^^^
评论
0赞
Maestro
2/12/2020
我可以看到的是,如果我单独定义它们,那么移动赋值不会调用移动构造函数,因为它接受右值引用,而二的 one 则按值传递。问题:哪个更有效:复制/移动赋值运算符还是定义单独的运算符?
1赞
Remy Lebeau
2/12/2020
@Maestro移动语义是有效的。您的类非常轻量级,因此无论是实现单个 copy+move 赋值运算符,还是实现两个单独的复制/移动运算符并不重要。显然,在指令数量上,实现单独的运算符可以避免对临时对象的一组构造函数/析构函数调用,但开销非常小,以至于不会真正成为问题。更重要的是能够编写可以随时间推移维护的代码。PInt
评论
"Done!"
operator=
copy-assignment operator
PInt
PInt& operator=(const PInt&)
PInt& operator=(PInt&&)