Почему нет присваивания / конструктора перемещения по умолчанию?


89

Я простой программист. Переменные-члены моего класса чаще всего состоят из POD-типов и STL-контейнеров. Из-за этого мне редко приходится писать операторы присваивания или конструкторы копирования, поскольку они реализованы по умолчанию.

Добавьте к этому, если я использую std::moveдля объектов, которые нельзя перемещать, он использует оператор присваивания, что означает std::moveсовершенно безопасно.

Поскольку я простой программист, я бы хотел воспользоваться возможностями перемещения, не добавляя конструктор перемещения / оператор присваивания к каждому написанному мной классу, поскольку компилятор мог бы просто реализовать их как " this->member1_ = std::move(other.member1_);..."

Но это не так (по крайней мере, в Visual 2010), есть ли для этого особая причина?

Важнее; есть ли способ обойти это?

Обновление: если вы посмотрите на ответ GManNickG, он предоставляет отличный макрос для этого. И если вы не знали, если вы реализуете семантику перемещения, вы можете удалить функцию-член подкачки.


5
вы знаете, что можете заставить компилятор сгенерировать ctor перемещения по умолчанию
aaronman

3
std :: move не выполняет перемещение, он просто преобразует l-значение в r-значение. Перемещение по-прежнему выполняется конструктором перемещения.
Оуэн Делахой 01

1
Ты о чем MyClass::MyClass(Myclass &&) = default;?
Сандберг

Да, в наши дни :)
Виктор Сер

Ответы:


76

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

Чтобы узнать больше об истории проблемы, см. Список статей WG21 2010 г. и выполните поиск по запросу "mov"

Текущая спецификация (N3225, от ноября) гласит (N3225 12.8 / 8):

Если определение класса Xявно не объявляет конструктор перемещения, он будет неявно объявлен как конструктор по умолчанию тогда и только тогда, когда

  • X не имеет конструктора копирования, объявленного пользователем, и

  • X не имеет объявленного пользователем оператора присваивания копии,

  • X не имеет объявленного пользователем оператора присваивания перемещения,

  • X не имеет деструктора, объявленного пользователем, и

  • конструктор перемещения не будет неявно определен как удаленный.

В 12.8 / 22 есть аналогичный язык, определяющий, когда оператор присваивания перемещения неявно объявляется как заданный по умолчанию. Вы можете найти полный список изменений, внесенных для поддержки текущей спецификации неявной генерации ходов в N3203: Ужесточение условий для генерации неявных ходов , которое было основано в основном на одном из решений, предложенных в статье Бьярна Страуструпа N3201: Движение вперед .


4
Я написал небольшую статью с некоторыми диаграммами, описывающими отношения для неявного (перемещающего) конструктора / присваивания здесь: mmocny.wordpress.com/2010/12/09/implicit-move-wont-go
mmocny

Ух, так что всякий раз, когда мне нужно определить пустые деструкторы в полиморфных базовых классах только для того, чтобы указать их как виртуальные, я также должен явно определить конструктор перемещения и оператор присваивания :(.
someguy

@ Джеймс МакНеллис: Я пробовал это раньше, но компилятору это не понравилось. Я собирался опубликовать сообщение об ошибке в этом самом ответе, но после попытки воспроизвести ошибку я понял, что в нем упоминается об этом cannot be defaulted *in the class body*. Итак, я определил деструктор снаружи, и он сработал :). Хотя мне это кажется немного странным. У кого-нибудь есть объяснение? Компилятор - gcc 4.6.1
someguy

3
Может быть, мы могли бы получить обновление этого ответа теперь, когда C ++ 11 ратифицирован? Любопытно, какое поведение победило.
Джозеф Гарвин,

2
@ Guy Avraham: Я думаю, что я говорил (прошло 7 лет), что если у меня есть объявленный пользователем деструктор (даже пустой виртуальный), конструктор перемещения не будет неявно объявлен как установленный по умолчанию. Я полагаю, это приведет к семантике копирования? (Я уже много лет не прикасался к C ++.) Затем Джеймс МакНеллис прокомментировал, что это virtual ~D() = default;должно работать и по-прежнему допускать неявный конструктор перемещения.
someguy

13

Неявно сгенерированные конструкторы перемещения рассматриваются в качестве стандарта, но могут быть опасными. См. Анализ Дэйва Абрахамса .

В конце концов, однако, стандарт действительно включал неявную генерацию конструкторов перемещения и операторов присваивания перемещения, хотя и с довольно существенным списком ограничений:

Если определение класса X явно не объявляет конструктор перемещения, он будет неявно объявлен как конструктор по умолчанию тогда и только тогда, когда
- X не имеет объявленного пользователем конструктора копирования,
- X не имеет объявленного пользователем оператора присваивания копии ,
- X не имеет объявленного пользователем оператора присваивания перемещения,
- X не имеет объявленного пользователем деструктора и
- конструктор перемещения не будет неявно определен как удаленный.

Однако это еще не все, что касается истории. Ctor можно объявить, но все же определить как удаленный:

Неявно объявленный конструктор копирования / перемещения является встроенным публичным членом своего класса. Конструктор копирования / перемещения по умолчанию для класса X определяется как удаленный (8.4.3), если X имеет:

- вариантный член с нетривиальным соответствующим конструктором, а X - класс, подобный объединению,
- нестатический член данных типа M (или его массив), который не может быть скопирован / перемещен из-за разрешения перегрузки (13.3), как применяется к соответствующему конструктору M, приводит к неоднозначности или к функции, которая удалена или недоступна из конструктора по умолчанию,
- прямой или виртуальный базовый класс B, который не может быть скопирован / перемещен из-за разрешения перегрузки (13.3), применительно к соответствующему конструктору B. , приводит к двусмысленности или к функции, которая удалена или недоступна из конструктора по умолчанию,
- любой прямой или виртуальный базовый класс или нестатический член данных типа с деструктором, который удален или недоступен из конструктора по умолчанию,
- для конструктора копирования нестатический член данных ссылочного типа rvalue или
- для конструктора перемещения нестатический член данных или прямой или виртуальный базовый класс с типом, который не имеет конструктора перемещения и не является тривиальным копируемый.


Текущий рабочий проект допускает неявную генерацию ходов при определенных условиях, и я думаю, что резолюция в значительной степени решает проблемы Абрахамса.
Джеймс МакНеллис

Я не уверен, что понял, какой ход может сломаться в примере между Tweak 2 и Tweak 3. Не могли бы вы объяснить это?
Matthieu M.

@Matthieu M .: и твик 2, и твик 3 сломаны, и на самом деле очень похоже. В Tweak 2 есть частные члены с инвариантами, которые могут быть нарушены ctor перемещения. В Tweak 3, класс не имеет частных пользователей самостоятельно , но так как он использует закрытое наследование, открытые и защищенные члены базы становятся закрытыми членами производный, ведущие к одной и той же проблемы.
Джерри Коффин

1
Я действительно не понимал, как конструктор перемещения нарушит инвариант класса Tweak2. Я полагаю, это как-то связано с тем фактом, что Numberобъект будет перемещен, а vectorобъект будет скопирован ... но я не уверен: / Я понимаю, что проблема передается каскадом Tweak3.
Matthieu M.

Ссылка, которую вы дали, кажется мертвой?
Wolf

8

(а пока работаю над дурацким макросом ...)

Да, я тоже пошел по этому пути. Вот ваш макрос:

// detail/move_default.hpp
#ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP
#define UTILITY_DETAIL_MOVE_DEFAULT_HPP

#include <boost/preprocessor.hpp>

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE(pR, pData, pBase) pBase(std::move(pOther))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE(pR, pData, pBase) pBase::operator=(std::move(pOther));

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR(pR, pData, pMember) pMember(std::move(pOther.pMember))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT(pR, pData, pMember) pMember = std::move(pOther.pMember);

#define UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        ,                                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)                                                   \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#endif

Взаимодействие с другими людьми

// move_default.hpp
#ifndef UTILITY_MOVE_DEFAULT_HPP
#define UTILITY_MOVE_DEFAULT_HPP

#include "utility/detail/move_default.hpp"

// move bases and members
#define UTILITY_MOVE_DEFAULT(pT, pBases, pMembers) UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)

// base only version
#define UTILITY_MOVE_DEFAULT_BASES(pT, pBases) UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)

// member only version
#define UTILITY_MOVE_DEFAULT_MEMBERS(pT, pMembers) UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)

#endif

(Я удалил настоящие комментарии, длинные и документальные.)

Вы указываете базы и / или члены в своем классе как список препроцессора, например:

#include "move_default.hpp"

struct foo
{
    UTILITY_MOVE_DEFAULT_MEMBERS(foo, (x)(str));

    int x;
    std::string str;
};

struct bar : foo, baz
{
    UTILITY_MOVE_DEFAULT_BASES(bar, (foo)(baz));
};

struct baz : bar
{
    UTILITY_MOVE_DEFAULT(baz, (bar), (ptr));

    void* ptr;
};

И выходит конструктор перемещения и оператор присваивания перемещения.

(Кстати, если кто-нибудь знает, как я могу объединить детали в один макрос, это было бы здорово.)


Большое спасибо, мой очень похож, за исключением того, что мне пришлось передать количество переменных-членов в качестве аргумента (что действительно отстой).
Виктор Сер

1
@Viktor: Нет проблем. Если еще не поздно, я думаю, вам стоит отметить один из других ответов как принятый. Мой был скорее «кстати, вот способ», а не ответом на ваш настоящий вопрос.
GManNickG

1
Если я правильно читаю ваш макрос, то, как только ваш компилятор реализует элементы перемещения по умолчанию, ваши приведенные выше примеры станут недоступными для копирования. Неявное создание копируемых членов запрещается, если присутствуют явно объявленные перемещаемые члены.
Говард Хиннант

@Howard: Ничего страшного, до тех пор это временное решение. :)
GManNickG

GMan: Этот макрос добавляет moveconstructor \ assign, если у вас есть функция подкачки:
Виктор Сер

4

VS2010 этого не делает, потому что на момент реализации они не были стандартными.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.