C ++ результат оператора = изменение после возврата * это - PullRequest
2 голосов
/ 28 февраля 2020

Здесь у меня есть очень простая программа, которая перемещает значение из одного объекта в другой, убирая значение из того, из которого оно было взято (оставляя после 0).

#include <iostream>

struct S
{
    S(char val) : m_val(val) {}
    S& operator=(S&& other) noexcept
    {
        this->m_val = other.m_val;
        other.m_val = '0';

        return *this;
    }
    char m_val = '0';
};

int main()
{
    S a('a');
    S b('b');

    std::cout << "a.m_val = '" << a.m_val << "'" << std::endl;
    std::cout << "b.m_val = '" << b.m_val << "'" << std::endl;

    a = std::move(b);

    std::cout << "a.m_val = '" << a.m_val << "'" << std::endl;
    std::cout << "b.m_val = '" << b.m_val << "'" << std::endl;

    return 0;
}

Как и ожидалось, результат этой программы:

a.m_val = 'a'
b.m_val = 'b'
a.m_val = 'b'
b.m_val = '0'

Значение 'b' передается от объекта b к объекту a, оставляя после себя '0'. Теперь, если я обобщу это немного с помощью шаблона, который (надеюсь) автоматически выполнит перенос и удаление бизнеса, вот что я в итоге получу ... (конечно, перегружено). запустить, вывод:

a.m_val = 'a'
b.m_val = 'b'
a.m_val = '0'
b.m_val = '0'

О, о! Каким-то образом ОБА объекты были "удалены". Когда я перебираю тело кода оператора присваивания перемещения ... все выглядит хорошо! a.m_val равен 'b', как мы и ожидали ... вплоть до оператора return *this;. Как только он возвращается из функции, внезапно это значение возвращается к «0». Кто-нибудь может пролить свет на то, почему это происходит?

Ответы [ 2 ]

3 голосов
/ 28 февраля 2020

Проблема в том, что S имеет неявно сгенерированный оператор присваивания перемещения , который вызывает оператор присваивания перемещения базового класса (то есть P<T>::operator=), а затем выполняет присваивание для каждого члена на члены (то есть S::m_val). В P<T>::operator= other.m_val назначено '0', затем обратно S::operator= this->m_val назначается other.m_val и становится '0'.

Вы можете определить пользователя -определенный оператор присваивания для S и ничего не ожидает вызова версии базового класса. например,

struct S : public P<S>
{
    S(char val) : m_val(val) {}

    char m_val = '0';

    S& operator=(S&& other) {
        P<S>::operator=(other);
        return *this;
    }
};

LIVE

3 голосов
/ 28 февраля 2020
P<T>& operator=(P<T>&& other) noexcept

Это явный оператор назначения перемещения для этого класса шаблона.

struct S : public P<S> {

Этот подкласс наследует от этого класса шаблона. P<S> является его родительским классом.

У этого подкласса нет явного оператора присваивания перемещения, поэтому ваш компилятор C ++ с готовностью создает для вас оператор присваивания перемещения по умолчанию, потому что именно так работает C ++. Оператор присваивания перемещения по умолчанию вызывает оператор присваивания перемещения родительского класса, а оператор присваивания перемещения по умолчанию затем назначает перемещение всем членам этого класса.

Просто потому, что у родительского класса есть явный оператор присваивания перемещения (ваш ход оператор присваивания) не делает этим оператор присваивания перемещения дочернего класса по умолчанию исчезает. Оператор присваивания перемещений по умолчанию S по сути таков, очень , говоря свободно:

S &operator=(S &&other)
{
    P<S>::operator=(std::move(other));
    this->m_val=std::move(other.m_val);

    return *this;
}

Это то, что вы получаете бесплатно от своего компилятора C ++. Разве не приятно, что ваш компилятор C ++ предоставляет такой полезный оператор присваивания перемещений по умолчанию для вашего класса?

a = std::move(b);

Это фактически приводит к вызову вышеуказанного оператора присвоения перемещений по умолчанию.

Какой сначала вызывает оператор присваивания перемещения родительского класса, тот, который вы написали.

, который фактически устанавливает other.m_val в '0'.

И когда он возвращается, этот оператор присваивания перемещения по умолчанию также устанавливает this->m_val до '0'.

...