Почему нет назначения-назначения/move-constructor по умолчанию?

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

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

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

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

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

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

Ответ 1

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

Подробнее об истории проблемы см. список статей WG21 за 2010 г. и найдите "mov"

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

Да, я тоже пошел по этому пути. Вот ваш макрос:
// 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;
};

И выходит команда move-constructor и move-assign.

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

Ответ 4

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