2011-01-27 5 views
78

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

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

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

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

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

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

+5

вы знаете, что вы можете иметь компилятор генерировать движение по умолчанию – aaronman

+1

станд т е р :: двигаться не выполнить ход, он просто отбрасывает от значения l до значения r. Перемещение по-прежнему выполняется конструктором перемещения. –

ответ

70

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

Более подробно об истории вопроса см the 2010 WG21 papers list и поиск "мы"

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

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

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

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

  • X не имеет оператора присваивания движения пользователя заявил,

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

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

Существует аналогичный язык в 12,8/22 с указанием, когда оператор присваивания шага неявно объявлен дефолтом. Вы можете найти полный список изменений, внесенных для поддержки текущей спецификации неявного генерации движений в N3203: Tightening the conditions for generating implicit moves , который был основан в основном на одной из резолюций, предложенных статьей Bjarne Stroustrup N3201: Moving right along.

+4

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

+0

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

+3

@someguy: 'virtual ~ D() = default;' должен работать. –

12

Неявно сгенерированные конструкторы перемещения считаются стандартными, но могут быть опасными. См. Dave Abrahams's analysis.

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

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

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

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

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

+0

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

+0

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

+0

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

4

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

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

И прибывает на перемещение-конструктор и двигаться-операторы присваивания.

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

+0

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

+1

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

+1

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

Смежные вопросы