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

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

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

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

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

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

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


person Viktor Sehr    schedule 27.01.2011    source источник
comment
вы знаете, что можете заставить компилятор сгенерировать ctor перемещения по умолчанию   -  person aaronman    schedule 05.09.2013
comment
std :: move не выполняет перемещение, он просто преобразует l-значение в r-значение. Перемещение по-прежнему выполняется конструктором перемещения.   -  person Owen Delahoy    schedule 01.10.2015
comment
Вы говорите о MyClass::MyClass(Myclass &&) = default;?   -  person Sandburg    schedule 03.09.2019
comment
Да, в наше время :)   -  person Viktor Sehr    schedule 23.03.2020


Ответы (4)


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

Подробнее об истории проблемы см. в документах WG21 2010 г. list и найдите mov

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

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

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

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

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

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

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

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

person James McNellis    schedule 27.01.2011
comment
Я написал небольшую статью с некоторыми диаграммами, описывающими отношения для неявного (перемещающего) конструктора / присваивания здесь: mmocny.wordpress.com/2010/12/09/implicit-move-wont-go - person mmocny; 26.04.2011
comment
Ух, так что всякий раз, когда мне нужно определить пустые деструкторы в полиморфных базовых классах только для того, чтобы указать их как виртуальные, я также должен явно определить конструктор перемещения и оператор присваивания :(. - person someguy; 10.08.2011
comment
@ Джеймс МакНеллис: Я пробовал это раньше, но компилятору это, похоже, не понравилось. Я собирался опубликовать сообщение об ошибке в этом самом ответе, но после попытки воспроизвести ошибку я понял, что в нем упоминается, что это cannot be defaulted *in the class body*. Итак, я определил деструктор снаружи, и он сработал :). Хотя я нахожу это немного странным. Есть у кого-нибудь объяснение? Компилятор - gcc 4.6.1 - person someguy; 10.08.2011
comment
@nawaz mov является преднамеренным: есть статья под названием «Двигаясь вправо», которую невозможно найти при поиске хода. - person James McNellis; 22.09.2013
comment
@JamesMcNellis: Ох, в этом случае перемещение - лучший термин, иначе кто-то другой мог бы прийти и изменить mov, чтобы двигаться снова, думая, что это опечатка. :-) Я бы предпочел название самого заголовка: Moving Right Along, потому что mov находит 16 результатов; какой из них вы намеревались? Все, несколько или ровно один? - person Nawaz; 22.09.2013
comment
@nawaz Все они. Некоторые используют движение, а другие - движение. Просто оставьте как есть, пожалуйста. - person James McNellis; 22.09.2013
comment
Может быть, мы могли бы получить обновление этого ответа теперь, когда C ++ 11 ратифицирован? Любопытно, какое поведение победило. - person Joseph Garvin; 25.02.2014
comment
Что об этом говорится в действующем стандарте? - person isarandi; 04.06.2014
comment
@someguy - Возможно, мне что-то не хватает, но я не понимаю, почему вам нужно явно определять конструктор перемещения и оператор присваивания в случае определения dtor в вашем базовом классе. (если предоставлены по умолчанию ctor перемещения и оператор присваивания, они действительно копируют семантику)? - person Guy Avraham; 13.09.2018
comment
@ Guy Avraham: Я думаю, что я говорил (прошло 7 лет), что если у меня есть объявленный пользователем деструктор (даже пустой виртуальный), конструктор перемещения не будет неявно объявлен как установленный по умолчанию. Я полагаю, это приведет к семантике копирования? (Я уже много лет не прикасался к C ++.) Затем Джеймс МакНеллис заметил, что virtual ~D() = default; должен работать и по-прежнему допускать неявный конструктор перемещения. - person someguy; 17.09.2018

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

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

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

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

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

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

person Jerry Coffin    schedule 27.01.2011
comment
Текущий рабочий проект действительно допускает неявную генерацию ходов при определенных условиях, и я думаю, что резолюция в значительной степени решает проблемы Абрахамса. - person James McNellis; 27.01.2011
comment
Я не уверен, что понял, какой ход может сломаться в примере между Tweak 2 и Tweak 3. Не могли бы вы это объяснить? - person Matthieu M.; 27.01.2011
comment
@Matthieu M .: и твик 2, и твик 3 сломаны, и на самом деле очень похоже. В Tweak 2 есть частные члены с инвариантами, которые могут быть нарушены ctor перемещения. В настройке 3 класс не имеет частных членов сам, но поскольку он использует частное наследование, открытые и защищенные члены базы становятся частными членами производного, что приводит к той же проблеме. - person Jerry Coffin; 27.01.2011
comment
Я действительно не понимал, как конструктор перемещения нарушит инвариант класса в Tweak2. Я полагаю, это как-то связано с тем, что Number будет перемещен, а vector будет скопирован ... но я не уверен: / Я понимаю, что проблема перейдет в Tweak3. - person Matthieu M.; 27.01.2011
comment
Ссылка, которую вы дали, кажется мертвой? - person Wolf; 27.06.2014
comment
Документ доступен по адресу open-std.org/ jtc1 / sc22 / wg21 / docs / paper / 2010 / n3153.htm, что похоже на тот же анализ. - person Ismael; 28.08.2014

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

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

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

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

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

person Community    schedule 28.01.2011
comment
Большое спасибо, мой очень похож, за исключением того, что мне пришлось передать количество переменных-членов в качестве аргумента (что действительно отстой). - person Viktor Sehr; 28.01.2011
comment
@Viktor: Нет проблем. Если еще не поздно, я думаю, вам стоит отметить один из других ответов как принятый. Мой был скорее кстати, вот способ, а не ответ на ваш настоящий вопрос. - person GManNickG; 28.01.2011
comment
Если я правильно читаю ваш макрос, то, как только ваш компилятор реализует элементы перемещения по умолчанию, ваши приведенные выше примеры станут не копируемыми. Неявное создание копируемых членов запрещается, если присутствуют явно объявленные перемещаемые члены. - person Howard Hinnant; 28.01.2011
comment
@Howard: Ничего страшного, до тех пор это временное решение. :) - person GManNickG; 28.01.2011
comment
GMan: этот макрос добавляет moveconstructor \ assign, если у вас есть функция подкачки: - person Viktor Sehr; 03.02.2012
comment
// Требуется: функция-член swap, может использоваться с некопируемыми объектами // Реализует: назначение перемещения и конструктор перемещения #define IMPLEMENTS_MOVEABLE () \ my_type (my_type && other) {this- ›swap (other); } \ my_type & operator = (my_type && other) {this- ›swap (другое); return * this; } - person Viktor Sehr; 03.02.2012
comment
@ViktorSehr: Это работает только тогда, когда все члены my_type правильно строят себя по умолчанию. Например, если у вас есть int член, он будет неинициализирован, а затем заменен, что приведет к UB. - person GManNickG; 03.02.2012
comment
@GManNickG: Ваш макрос также работает лучше, после некоторого тестирования я обнаружил, что предоставленный мной макрос выполняет больше перемещений участников, чем ваш. Отличная работа! - person Viktor Sehr; 04.03.2012
comment
Вы забыли #pragma один раз в move_default.hpp thou =) - person Viktor Sehr; 04.03.2012
comment
@ViktorSehr: Я не использую #pragma once, только защиту заголовков. - person GManNickG; 05.03.2012
comment
@GManNickG: переписал ваш макрос, чтобы также создать конструктор копирования, регулярное присваивание и оператор == \ operator! =. Теперь я использую его повсюду =) Делает мои классы намного чище и с меньшим количеством ошибок. Спасибо! - person Viktor Sehr; 07.03.2012

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

person Puppy    schedule 27.01.2011