Это плохая практика для перемещения ссылочного параметра l-значения? - PullRequest
6 голосов
/ 24 июня 2019

Первоначально я предполагал, что перемещать эталонный параметр l-значения - плохая практика.Действительно ли это общепринято сообществом разработчиков C ++?

Когда я вызываю функцию, имеющую ссылочный параметр R-значения, ясно, что я должен ожидать, что переданный объект может быть перемещен.Для функции, имеющей параметр ссылки на L-значение, это не так очевидно (и до того, как семантика перемещения была введена в C ++ 11, это вообще было невозможно).

Однако некоторые другие разработчикиЯ недавно говорил, чтобы не согласиться с тем, что следует избегать перемещения ссылок на l-значение.Есть ли веские аргументы против этого?Или мое мнение неверно?

Поскольку меня попросили предоставить пример кода, вот один (см. Ниже).Это искусственный пример только для демонстрации проблемы.Очевидно, что после вызова modifyCounter2 () вызов getValue () вызовет ошибку сегментации.Однако, если бы я был пользователем getValue (), не зная его внутренней реализации, я был бы очень удивлен.Если бы, с другой стороны, параметр был ссылкой на R-значение, я бы совершенно ясно, что я не должен больше использовать объект после вызова modifyCounter2 ().

class Counter
{
public:
    Counter() : value(new int32_t(0))
    {
        std::cout << "Counter()" << std::endl;
    }

    Counter(const Counter & other) : value(new int32_t(0))
    {
        std::cout << "Counter(const A &)" << std::endl;

        *value = *other.value;
    }

    Counter(Counter && other)
    {
        std::cout << "Counter(Counter &&)" << std::endl;

        value = other.value;
        other.value = nullptr;
    }

    ~Counter()
    {
        std::cout << "~Counter()" << std::endl;

        if (value)
        {
            delete value;
        }
    }

    Counter & operator=(Counter const & other)
    {
        std::cout << "operator=(const Counter &)" << std::endl;

        *value = *other.value;

        return *this;
    }

    Counter & operator=(Counter && other)
    {
        std::cout << "operator=(Counter &&)" << std::endl;

        value = other.value;
        other.value = nullptr;

        return *this;
    }

    int32_t getValue()
    {
        return *value;
    }

    void setValue(int32_t newValue)
    {
        *value = newValue;
    }

    void increment()
    {
        (*value)++;
    }

    void decrement()
    {
        (*value)--;
    }

private:
    int32_t* value;      // of course this could be implemented without a pointer, just for demonstration purposes!
};

void modifyCounter1(Counter & counter)
{
    counter.increment();
    counter.increment();
    counter.increment();
    counter.decrement();
}

void modifyCounter2(Counter & counter)
{
    Counter newCounter = std::move(counter);
}

int main(int argc, char ** argv)
{
    auto counter = Counter();

    std::cout << "value: " << counter.getValue() << std::endl;

    modifyCounter1(counter);  // no surprises

    std::cout << "value: " << counter.getValue() << std::endl;

    modifyCounter2(counter);  // surprise, surprise!

    std::cout << "value: " << counter.getValue() << std::endl;

    return 0;
}

1 Ответ

10 голосов
/ 24 июня 2019

Да, это удивительно и нетрадиционно.

Если вы хотите разрешить перемещение, условием является проходить по значению . Вы можете std::move изнутри функции соответствующим образом, в то время как вызывающая сторона может std::move в аргумент, если они решат, что хотят утратить право собственности.

Это соглашение - то, откуда пришло понятие назвать std::move таким образом: содействовать явному конфискации собственности, что означает намерение. Вот почему мы миримся с именем, хотя оно вводит в заблуждение (std::move ничего не меняет).

Но твой путь - воровство. ;)

...