Может ли std :: move вызывать нарезку при переходе к ссылке на l-значение? - PullRequest
3 голосов
/ 10 мая 2019

См. Следующий код:

#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 изменяетсяв результате перемещения экземпляра производного класса в ссылку на родительский класс, но j этого не происходит.

Является ли результат, напечатанный в (2), признаком того, что перемещение в (1) вызвалонарезка?Или какое-то другое поведение (или даже неопределенное поведение) начинает действовать?

Ответы [ 2 ]

6 голосов
/ 10 мая 2019

Может std :: move вызвать нарезку ...

Нет. std::move не вызывает нарезки.

Однако, назначение в базовый объект вызывает нарезку, т. Е. Назначается только основание, а остальная часть объекта не изменяется. Это происходит как при копировании, так и при перемещении. Присвоение перемещения, однако, требует дополнительного рассмотрения: не только назначена основа левого операнда (как в случае копирования), но также перемещена только основа правого операнда.

Независимо от того, назначена ли база через ссылку или нет, это не влияет на нарезку, если оператор присваивания не является виртуальным (хотя не используйте виртуальные операторы присваивания; они не являются простым / хорошим решением).


Или:

  • Убедитесь, что производные классы могут работать с назначенными / перемещенными из базовых подобъектами (т.е. не должно быть никаких инвариантов классов, которые могли бы быть нарушены таким назначением).
  • Или сделать базу недоступной для назначения (возможно, сделать назначение защищенным).
  • Или сделать базу недоступной (защищенной или частной)

В любом случае, убедитесь, что статический тип левого операнда соответствует назначению.

5 голосов
/ 10 мая 2019

Да, нарезка происходит, потому что оператор присваивания перемещения выбран статически (во время компиляции), а статический тип левой стороны равен Parent&, а не Child:

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

Для пояснениявы не «переходите в lvalue-ссылку».Вы перемещаетесь в объект , но не используете функцию, которая перемещает весь объект (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()
...