Ответ 1
Кажется, на самом деле возникает вопрос: как можно реализовать конструктор перемещения и назначение переноса с помощью компиляторов С++ 03?
Простой ответ: они не могут! Тем не менее, простой ответ пренебрегает возможностью создания чего-то совершенно корректного кода С++ 03 и который становится конструктором перемещения и перемещает назначение с помощью компилятора С++ 11. Этот подход должен будет использовать некоторые хакеры препроцессора, но этот бит используется только для создания заголовка, определяющего несколько инструментов, используемых для фактической реализации.
Вот простой заголовочный файл, который с радостью компилируется без каких-либо предупреждений с clang и gcc с включенным или отключенным С++ 11:
// file: movetools.hpp
#ifndef INCLUDED_MOVETOOLS
#define INCLUDED_MOVETOOLS
INCLUDED_MOVETOOLS
namespace mt
{
#if __cplusplus < 201103L
template <typename T>
class rvalue_reference {
T* ptr;
public:
rvalue_reference(T& other): ptr(&other) {}
operator T&() const { return *this->ptr; }
};
#else
template <typename T>
using rvalue_reference = T&&;
#endif
template <typename T>
rvalue_reference<T> move(T& obj) {
return static_cast<rvalue_reference<T> >(obj);
}
}
#endif
Основная особенность заключается в определении шаблона mt::rvalue_reference<T>
, который ведет себя как ссылка rvalue в С++ 03 и фактически является ссылкой rvalue (т.е. a T&&
) для С++ 11. Он не будет иметь дело с С++ 03 rvalue-ссылками, но, по крайней мере, позволяет создавать конструкторы перемещения и переводить назначения, не требуя при этом ссылок на rvalue.
Обратите внимание, что mt::move()
просто используется, чтобы позже показать, как rvalue_reference<T>
можно перемещать даже в С++ 03! Главное, что rvalue_reference<T>
- это то, что понимает компилятор С++ 03 или T&&
. Для этой вполне разумной нотации необходимо, чтобы компилятор поддерживал шаблоны псевдонимов. Если это не так, можно применить тот же трюк, но с использованием подходящего вложенного типа соответствующего шаблона класса.
Вот пример использования этого заголовка:
#include "movetools.hpp"
#include <iostream>
class foo
{
public:
foo() { std::cout << "foo::foo()\n"; }
foo(foo const&) { std::cout << "foo::foo(const&)\n"; }
foo(mt::rvalue_reference<foo> other) {
std::cout << "foo::foo(&&)\n";
this->swap(other);
}
~foo() { std::cout << "foo::~foo()\n"; }
foo& operator= (foo const& other) {
std::cout << "foo::operator=(foo const&)\n";
foo(other).swap(*this);
return *this;
}
foo& operator= (mt::rvalue_reference<foo> other) {
std::cout << "foo::operator=(foo&&)\n";
this->swap(other);
return *this;
}
void swap(foo&) {
std::cout << "foo::swap(foo&)\n";
}
};
int main()
{
foo f0;
foo f1 = f0;
foo f2 = mt::move(f0);
f1 = f2;
f0 = mt::move(f1);
}
То есть, фактическая бизнес-логика лишена какого-либо хакерства препроцессора. Единственное, что нужно беспокоить в препроцессоре, - это заголовок movetools.hpp
, который не нужно смешивать. То есть я на самом деле думаю, что он не использует хакеры препроцессора для определения фактического конструктора перемещения или назначения переноса, хотя препроцессор используется где-то. Если вы настаиваете на том, что вы не хотите использовать макро-хакерство, это можно сделать, направляя компилятор посмотреть на разные заголовки, но что деталь реализации movetools.hpp
.