Почему нет назначения-назначения/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 этого не делает, потому что они не были стандартными во время реализации.