移到L值参考中时,std :: move会导致切片吗?

时间:2019-05-10 11:46:38

标签: c++ c++11 move-semantics object-slicing

请参见以下代码:

<bindings>
 <binding protocol="http" bindingInformation="*:8080:Your local Ip"/>
</bindings>

当我运行它时,我得到:

#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却没有变化。

(2)中打印的结果是否表示(1)中的移动引起切片?还是其他一些行为(甚至是未定义的行为)开始发生?

2 个答案:

答案 0 :(得分:6)

  

可以std :: move导致切片...

不。 std::move不会导致切片。

但是,将 assignment 分配给基础对象会导致切片,即仅分配了基础,而对象的其余部分不受影响。复制分配和移动分配时都会发生这种情况。但是,移动分配确实需要额外考虑:不仅分配了左操作数的基(如在复制的情况下),而且还仅移了了右操作数的基。

除非通过赋值运算符是虚拟的,否则是否通过引用分配基数都不会影响切片(不过不要使用虚拟赋值运算符;它们不是一个简单/好的解决方案)。


要么:

  • 确保派生类可以处理从基础子对象分配/移动的对象(即,不应有此类分配可能违反的类不变式)。
  • 或者使基础不可分配(也许使分配受保护)。
  • 或使基础无法访问(受保护或私有)

无论如何,请确保左操作数的静态类型与分配时所期望的一样。

答案 1 :(得分:5)

是的,发生切片是因为静态地选择了移动分配操作符(在编译时),并且左侧的静态类型是Parent&,而不是Child

Child c;
Child c2;
Parent& p_ref = c2;
p_ref = std::move(c);   // (1)

为澄清起见,您没有“进入左值引用”。您进入 object ,但不使用移动整个对象(Child::operator=)的函数,而是仅移动Parent部分(Parent::operator=)的函数。

尤其是,移动语义没有什么特别的。相同的行为将适用于任何成员函数。在这种情况下,右侧运算符的类型无关紧要。

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()