Почему по умолчанию нет перемещения-назначения / перемещения-конструктора? - PullRequest
87 голосов
/ 27 января 2011

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

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

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

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

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

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

Ответы [ 4 ]

74 голосов
/ 27 января 2011

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

1002 * ДляПодробнее об истории вопроса см. список работ WG21 за 2010 год и поиск по запросу "mov"

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

Если определение класса X явно не объявляет конструктор перемещения, он будет неявно объявлен как дефолтный, если и только если

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

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

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

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

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

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

12 голосов
/ 27 января 2011

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

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

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

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

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

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

8 голосов
/ 28 января 2011

(сейчас я работаю над глупым макросом ...)

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

// 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;
};

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

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

4 голосов
/ 27 января 2011

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

...