std::move 移动到左值引用时会导致切片吗?
Can std::move cause slicing when moving into l-value reference?
见以下代码:
#include <iostream>
#include <chrono>
class Parent
{
public:
Parent() = default;
virtual ~Parent() = default;
Parent(const Parent& pr) : i{pr.i} {std::cout << "Parent copy constructor\n";}
Parent& operator=(const Parent& pr) {std::cout << "Parent copy assignment\n"; this->i = pr.i; return *this;}
Parent(Parent&& pr) : i{std::move(pr.i)} {std::cout << "Parent move constructor\n";}
Parent& operator=(Parent&& pr) {std::cout << "Parent move assignment\n"; this->i = std::move(pr.i); return *this;}
virtual void print_i_j() = 0;
int i = 10;
};
class Child : public Parent
{
public:
Child() = default;
Child(const Child& cr) : Parent{cr}, j{cr.j} {std::cout << "Child copy constructor\n";}
Child& operator=(const Child& cr) {std::cout << "Child copy assignment\n"; this->j = cr.j; return *this;}
Child(Child&& cr) : Parent{std::move(cr)}, j{std::move(cr.j)} {std::cout << "Child move constructor\n";}
Child& operator=(Child&& cr) {std::cout << "Child move assignment\n"; Parent::operator=(std::move(cr)); this->j = std::move(cr.j); return *this;}
void print_i_j() {std::cout << "i = "<< i << " j = " << j << std::endl;}
int j = 100;
};
int main(int argc, const char * argv[])
{
Child c;
c.i = 30;
c.j = 300;
c.print_i_j();
Child c2; // leave c2 with defaults (i=10, j=100)
Parent& p_ref = c2;
p_ref.print_i_j();
c2.j = 150;
p_ref.print_i_j();
p_ref = std::move(c); // (1)
p_ref.print_i_j(); // (2)
return 0;
}
当我 运行 我得到:
i = 30 j = 300
i = 10 j = 100
i = 10 j = 150
Parent move assignment
i = 30 j = 150
据我所知,如输出所示,i
由于将派生 class 的实例移动到对父 class 的引用而发生变化, 但 j
没有。
(2)中打印的结果是否表明(1)中的移动导致了切片?还是其他一些行为(甚至未定义的行为)开始起作用?
是的,发生切片是因为移动赋值运算符是静态选择的(在编译时),而left-hand端的静态类型是Parent&
,而不是Child
:
Child c;
Child c2;
Parent& p_ref = c2;
p_ref = std::move(c); // (1)
澄清一下,你没有 "move into lvalue-reference"。您移动到 对象 ,但不使用移动整个对象 (Child::operator=
) 的函数,而是使用仅移动 Parent
部分的函数 (Parent::operator=
).
特别是,移动语义没有什么特别之处;相同的行为将适用于任何成员函数。 right-hand 侧运算符的类型在这种情况下不相关。
class Parent
{
public:
virtual ~Parent() = default;
void func(); // non-virtual, like move assignment
};
class Child : public Parent
{
public:
void func();
};
// usage:
Child c;
Parent& p_ref = c;
p_ref.func(); // calls Parent::func(), not Child::func()
Can std::move cause slicing ...
没有。 std::move
不会导致切片。
但是,赋值到基础对象中会导致切片,即只有基础被赋值,对象的其余部分不受影响。这在复制分配和移动分配时都会发生。然而,移动赋值确实有一个额外的考虑:不仅分配了左手操作数的基数(就像在复制的情况下),而且还只移动了右手操作数的基数。
是否通过引用分配基数不会影响切片,除非赋值运算符是虚拟的(尽管不要使用虚拟赋值运算符;它们不是一个简单/好的解决方案)。
或者:
- 确保派生的 classes 可以处理从基础子对象分配/移动的对象(即不应该有可能被此类分配违反的 class 不变量)。
- 或者使基数不可分配(也许使分配受到保护)。
- 或使基地不可访问(受保护或私有)
在任何情况下,请确保左侧操作数的静态类型是您在分配时期望的类型。
见以下代码:
#include <iostream>
#include <chrono>
class Parent
{
public:
Parent() = default;
virtual ~Parent() = default;
Parent(const Parent& pr) : i{pr.i} {std::cout << "Parent copy constructor\n";}
Parent& operator=(const Parent& pr) {std::cout << "Parent copy assignment\n"; this->i = pr.i; return *this;}
Parent(Parent&& pr) : i{std::move(pr.i)} {std::cout << "Parent move constructor\n";}
Parent& operator=(Parent&& pr) {std::cout << "Parent move assignment\n"; this->i = std::move(pr.i); return *this;}
virtual void print_i_j() = 0;
int i = 10;
};
class Child : public Parent
{
public:
Child() = default;
Child(const Child& cr) : Parent{cr}, j{cr.j} {std::cout << "Child copy constructor\n";}
Child& operator=(const Child& cr) {std::cout << "Child copy assignment\n"; this->j = cr.j; return *this;}
Child(Child&& cr) : Parent{std::move(cr)}, j{std::move(cr.j)} {std::cout << "Child move constructor\n";}
Child& operator=(Child&& cr) {std::cout << "Child move assignment\n"; Parent::operator=(std::move(cr)); this->j = std::move(cr.j); return *this;}
void print_i_j() {std::cout << "i = "<< i << " j = " << j << std::endl;}
int j = 100;
};
int main(int argc, const char * argv[])
{
Child c;
c.i = 30;
c.j = 300;
c.print_i_j();
Child c2; // leave c2 with defaults (i=10, j=100)
Parent& p_ref = c2;
p_ref.print_i_j();
c2.j = 150;
p_ref.print_i_j();
p_ref = std::move(c); // (1)
p_ref.print_i_j(); // (2)
return 0;
}
当我 运行 我得到:
i = 30 j = 300
i = 10 j = 100
i = 10 j = 150
Parent move assignment
i = 30 j = 150
据我所知,如输出所示,i
由于将派生 class 的实例移动到对父 class 的引用而发生变化, 但 j
没有。
(2)中打印的结果是否表明(1)中的移动导致了切片?还是其他一些行为(甚至未定义的行为)开始起作用?
是的,发生切片是因为移动赋值运算符是静态选择的(在编译时),而left-hand端的静态类型是Parent&
,而不是Child
:
Child c;
Child c2;
Parent& p_ref = c2;
p_ref = std::move(c); // (1)
澄清一下,你没有 "move into lvalue-reference"。您移动到 对象 ,但不使用移动整个对象 (Child::operator=
) 的函数,而是使用仅移动 Parent
部分的函数 (Parent::operator=
).
特别是,移动语义没有什么特别之处;相同的行为将适用于任何成员函数。 right-hand 侧运算符的类型在这种情况下不相关。
class Parent
{
public:
virtual ~Parent() = default;
void func(); // non-virtual, like move assignment
};
class Child : public Parent
{
public:
void func();
};
// usage:
Child c;
Parent& p_ref = c;
p_ref.func(); // calls Parent::func(), not Child::func()
Can std::move cause slicing ...
没有。 std::move
不会导致切片。
但是,赋值到基础对象中会导致切片,即只有基础被赋值,对象的其余部分不受影响。这在复制分配和移动分配时都会发生。然而,移动赋值确实有一个额外的考虑:不仅分配了左手操作数的基数(就像在复制的情况下),而且还只移动了右手操作数的基数。
是否通过引用分配基数不会影响切片,除非赋值运算符是虚拟的(尽管不要使用虚拟赋值运算符;它们不是一个简单/好的解决方案)。
或者:
- 确保派生的 classes 可以处理从基础子对象分配/移动的对象(即不应该有可能被此类分配违反的 class 不变量)。
- 或者使基数不可分配(也许使分配受到保护)。
- 或使基地不可访问(受保护或私有)
在任何情况下,请确保左侧操作数的静态类型是您在分配时期望的类型。