Как и любое усилие эмуляции, библиотека имеет некоторые ограничения, которые пользователи должны учитывать при использовании библиотеки с компиляторами, соответствующими C++03:
При инициализации базовых классов в конструкторах перемещения пользователи должны отлить ссылку на ссылку базового класса перед ее перемещением или просто использовать<BOOST_MOVE_BASE
>. Пример:
Derived(BOOST_RV_REF(Derived) x)
: Base(boost::move(static_cast<Base&>(x)))
или
Derived(BOOST_RV_REF(Derived) x)
: Base(BOOST_MOVE_BASE(Base, x))
Если литье не выполняется, эмуляция не будет перемещать конструкцию базового класса, поскольку преобразование не доступно с<BOOST_RV_REF(Derived)
>до<BOOST_RV_REF(Base)
>. Без литья или<BOOST_MOVE_BASE
>мы можем получить ошибку компиляции (для некопируемых типов) или менее эффективный конструктор ходов (для копируемых типов):
Derived(BOOST_RV_REF(Derived) x)
: Base(boost::move(x))
Эмуляция не может иметь дело с правилами свертывания ссылок C++0x, которые позволяют идеальную пересылку:
template<class T>
void forward_function(T &&t)
{ inner_function(std::forward<T>(t); }
template<class T>
void forward_function(BOOST_RV_REF<T> t)
{ inner_function(boost::forward<T>(t); }
В C++03 эмуляция BOOST_RV_REF не улавливает никаких const rlvalues. Подробнее о пересылке см.Конструкторская экспедицияглава.
Предложениепервой ссылки на rvalueпозволило привязать ссылки на rvalue к lvalues:
func(Type &&t);
Type t;
func(t)
Позже, как объясняется вУстранение проблемы безопасности с помощью ссылок на значения, такое поведение считалось опасным и устраняло это связывание, так что ссылки на значения r придерживались принципа безопасной перегрузки по типу:Каждая функция должна быть безопасна по типу, независимо от того, как она была перегружена.
Boost.Moveне может эмулировать этот принцип безопасной перегрузки для компиляторов C++03:
movable m;
BOOST_RV_REF(movable) r = m;
Макрос<BOOST_COPYABLE_AND_MOVABLE
>должен определить конструктор копий для<copyable_and_movable
>взятия неконст-параметра в компиляторах C++03:
copyable_and_movable &operator=(copyable_and_movable&){/**/}
Поскольку генерируется неконстная перегрузка конструктора копий, операторы присваивания, генерируемые компилятором для классов, содержащих<copyable_and_movable
>, получат неконст-конструкторскую перегрузку, которая наверняка удивит пользователей:
class holder
{
copyable_and_movable c;
};
void func(const holder& h)
{
holder copy_h(h);
}
Это ограничение заставляет пользователя определять конст-версию присвоения копии во всех классах, содержащих копируемые и подвижные классы, которые могут раздражать в некоторых случаях.
Альтернативой является реализация единственного<operator
=()
>для копируемых и подвижных классовс использованием семантики «pass by value»:
T& operator=(T x)
{
swap(*this, x);
return *this;
}
Однако «переход по значению» не является оптимальным для классов (например, контейнеров, строк и т.д.), которые повторно используют ресурсы (например, ранее выделенную память), когда x присваивается из lvalue.
При наличии подвижного и копируемого класса, если добавлен шаблонный оператор назначения (*):
class Foo
{
BOOST_COPYABLE_AND_MOVABLE(Foo)
public:
int i;
explicit Foo(int val) : i(val) {}
Foo(BOOST_RV_REF(Foo) obj) : i(obj.i) {}
Foo& operator=(BOOST_RV_REF(Foo) rhs)
{ i = rhs.i; rhs.i = 0; return *this; }
Foo& operator=(BOOST_COPY_ASSIGN_REF(Foo) rhs)
{ i = rhs.i; return *this; }
template<class U>
Foo& operator=(const U& rhs)
{ i = -rhs.i; return *this; }
};
Компиляторы C++98 и C++11 будут вести себя по-разному при присвоении значения<[const]
Foo
>:
Foo foo1(1);
Foo foo2(2);
foo2 = foo1;
const Foo foo5(5);
foo2 = foo5;
Это различное поведение является побочным эффектом эмуляции движения, которого нелегко избежатьBoost.Move. Одним из обходных путей является SFINAE-устранение шаблонного оператора назначения с<disable_if
>:
template<class U>
typename boost::disable_if<boost::is_same<U, Foo>, Foo&>::type
operator=(const U& rhs)
{ i = -rhs.i; return *this; }