Почему нет по умолчанию перемещения-назначения / перемещения-конструктора?
Я простой программист. Мои переменные членов класса чаще всего состоят из POD-типов и STL-контейнеров. Из-за этого мне редко приходится писать операторы присваивания или конструкторы копирования, так как они реализованы по умолчанию.
Добавьте к этому, если я использую std::move
на объектах, которые не являются подвижными, он использует оператор присваивания, то есть std::move
совершенно безопасно.
Поскольку я простой программист, я бы хотел воспользоваться возможностями перемещения, не добавляя конструктор перемещения / оператор присваивания в каждый класс, который я пишу, так как компилятор мог просто реализовать их как "this->member1_ = std::move(other.member1_);...
"
Но это не так (по крайней мере, в Visual 2010), есть ли какая-то конкретная причина для этого?
Важнее; есть ли способ обойти это?
Обновление: если вы посмотрите вниз на ответ GManNickG, он предоставит отличный макрос для этого. И если вы не знали, если вы реализуете семантику перемещения, вы можете удалить функцию-член swap.
4 ответа
Неявное поколение хода конструкторов и операторы присваивания было спорным и произошло значительные изменения в последних проектах стандарта C++, поэтому в настоящее время доступных компиляторы, вероятно, ведут себя по-разному по отношению к неявному поколению.
Для получения дополнительной информации об истории вопроса см. Список работ WG21 за 2010 год и выполните поиск по запросу "mov".
Текущая спецификация (N3225 от ноября) гласит (N3225 12.8/8):
Если определение класса
X
явно не объявляет конструктор перемещения, он будет неявно объявлен как дефолт, если и только если
X
не имеет объявленного пользователем конструктора копирования, и
X
не имеет заявленного пользователем оператора копирования,
X
не имеет объявленного пользователем оператора назначения перемещения,
X
не имеет объявленного пользователем деструктора, иконструктор перемещения не будет неявно определен как удаленный.
В 12.8/22 есть похожий язык, определяющий, когда оператор присваивания перемещения неявно объявляется как дефолтный. Вы можете найти полный список изменений, внесенных для поддержки текущей спецификации генерации неявных движений в N3203: Ужесточение условий для генерации неявных движений, которая была основана главным образом на одном из разрешений, предложенных в статье Бьярна Страуструпа N3201: Двигаться вправо.
Неявно сгенерированные конструкторы перемещения были рассмотрены для стандарта, но могут быть опасными. Смотрите анализ Дэйва Абрахамса.
В конце концов, однако, стандарт включал неявную генерацию конструкторов перемещения и операторов назначения перемещения, хотя и с довольно существенным списком ограничений:
Если определение класса X явно не объявляет конструктор перемещения, он будет неявно объявлен как дефолтный, если и только если
- X не имеет объявленного пользователем конструктора копирования,
- X не имеет заявленного пользователем оператора копирования,
- X не имеет объявленного пользователем оператора назначения перемещения,
- X не имеет объявленного пользователем деструктора, и
- конструктор перемещения не будет неявно определен как удаленный.
Это еще не все, что есть в истории. Ctor может быть объявлен, но все еще определен как удаленный:
Неявно объявленный конструктор копирования / перемещения является встроенным открытым членом своего класса. По умолчанию конструктор копирования / перемещения для класса X определяется как удаленный (8.4.3), если X имеет:
- вариантный член с нетривиальным соответствующим конструктором и X является объединяющим классом,
- нестатический член данных типа класса M (или его массив), который не может быть скопирован / перемещен, потому что разрешение перегрузки (13.3) применительно к соответствующему конструктору M приводит к неоднозначности или функции, которая удаляется или недоступна из дефолтный конструктор,
- прямой или виртуальный базовый класс B, который нельзя скопировать / переместить, поскольку разрешение перегрузки (13.3) применительно к соответствующему конструктору B приводит к неоднозначности или функции, которая удаляется или недоступна из конструктора по умолчанию,
- любой прямой или виртуальный базовый класс или нестатический член данных типа с деструктором, который удален или недоступен из конструктора по умолчанию,
- для конструктора копирования, нестатического члена данных ссылочного типа rvalue, или
- для конструктора перемещения - нестатический элемент данных или прямой или виртуальный базовый класс с типом, который не имеет конструктора перемещения и который не может быть легко скопирован.
(на данный момент я работаю над глупым макросом...)
Да, я тоже пошел по этому пути. Вот твой макрос:
// 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-конструктор и оператор присваивания.
(Кроме того, если кто-нибудь знает, как я мог бы объединить детали в один макрос, это было бы здорово.)
VS2010 не делает этого, потому что они не были Стандартными во время реализации.