Чому немає стандартного переміщення / конструктора переміщення?


89

Я простий програміст. Змінні членів мого класу найчастіше складаються з типів POD та STL-контейнерів. Через це мені рідко доводиться писати оператори присвоєння або конструктори копіювання, оскільки вони реалізовані за замовчуванням.

Додайте до цього, якщо я використовую std::moveна об'єктах, що не рухаються, він використовує оператор присвоєння, тобто std::moveабсолютно безпечний.

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

Але це не так (принаймні не у Visual 2010), чи є для цього якась особлива причина?

Що ще більш важливо; чи є спосіб обійти це?

Оновлення: Якщо ви дивитесь на відповідь GManNickG, він пропонує чудовий макрос для цього. І якщо ви не знали, якщо ви впровадите семантику переміщення, ви можете видалити функцію члена підкачки.


5
Ви знаєте, що можете
запросити

3
std :: move не виконує переміщення, він просто перетворює значення l на значення r. Переміщення все ще виконується конструктором переміщення.
Оуен Делахой,

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

@James McNellis: Це те, що я раніше пробував, але компіляторові це не подобалося. Я збирався опублікувати повідомлення про помилку саме в цій відповіді, але після спроби відтворити помилку я зрозумів, що в ній згадується 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 не має оголошеного користувачем деструктора, і
- конструктор переміщення не буде неявно визначений як видалений.

Це не зовсім все, що стосується історії. Ктор можна оголосити, але все одно визначити як видалений:

Явно оголошений конструктор копіювання / переміщення - це вбудований загальнодоступний член свого класу. Конструктор копіювання / переміщення за замовчуванням для класу X визначається як видалений (8.4.3), якщо X має:

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


Поточний робочий проект дійсно дозволяє імпліцитно генерувати переміщення за певних умов, і я думаю, що резолюція в основному враховує занепокоєння Абрахамса.
James McNellis

Я не впевнений, що зрозумів, який крок може зламатись у прикладі між Tweak 2 і Tweak 3. Чи можете ви пояснити це?
Matthieu M.

@Matthieu M.: І Tweak 2, і Tweak 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

U

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

І виходить конструктор переміщення та оператор присвоєння переміщення.

(Окрім того, якщо хтось знає, як я міг поєднати деталі в один макрос, це було б чудово.)


Щиро дякую, моя досить схожа, за винятком того, що мені довелося передавати кількість змінних-членів як аргумент (що насправді відмовно).
Viktor Sehr

1
@Viktor: Немає проблем. Якщо ще не пізно, думаю, вам слід позначити одну з інших відповідей як прийняту. Моє було скоріше "до речі, ось шлях", а не відповідь на ваше справжнє запитання.
GManNickG

1
Якщо я правильно читаю ваш макрос, то як тільки ваш компілятор реалізує стандартні елементи переміщення, ваші наведені вище приклади стануть неможливо скопіювати. Неявна генерація членів копії забороняється, коли є явно оголошені члени переміщення.
Говард Хіннант

@Howard: Це нормально, це тимчасове рішення до тих пір. :)
GManNickG

GMan: Цей макрос додає moveconstructor \ assign, якщо у вас є функція підкачки:
Viktor Sehr

4

VS2010 цього не робить, оскільки на момент впровадження вони не були стандартними.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.