Повторное использование идиомы копирования и свопинга
Я пытаюсь поместить идиому copy-and-swap в многоразовый mixin:
template<typename Derived>
struct copy_and_swap
{
Derived& operator=(Derived copy)
{
Derived* derived = static_cast<Derived*>(this);
derived->swap(copy);
return *derived;
}
};
Я намерен его смешивать через CRTP:
struct Foo : copy_and_swap<Foo>
{
Foo()
{
std::cout << "default\n";
}
Foo(const Foo& other)
{
std::cout << "copy\n";
}
void swap(Foo& other)
{
std::cout << "swap\n";
}
};
Однако простой тест показывает, что он не работает:
Foo x;
Foo y;
x = y;
Это только дважды печатает "по умолчанию", не печатается ни "копия", ни "своп". Что мне здесь не хватает?
Ответы
Ответ 1
Я боюсь, что это одна область, где необходим макрос, из-за сложных правил о автоматически созданных операциях копирования и присваивания.
Независимо от того, что вы делаете, вы находитесь в любом из двух случаев:
- Вы предоставили (явно) объявление оператора присваивания, и в этом случае вы также должны предоставить определение
- Вы не предоставили (явно) объявление оператора присваивания, и в этом случае компилятор будет генерировать его, если базовые классы и нестатические члены имеют один доступный.
Таким образом, следующий вопрос: стоит ли автоматизировать такое письмо?
Copy-And-Swap используется только для очень специфических классов. Я не думаю, что это того стоит.
Ответ 2
Это:
Derived& operator=(Derived copy)
не объявляет оператор присваивания копии базовому классу (он имеет неправильную подпись). Таким образом, созданный по умолчанию оператор присваивания в Foo
не будет использовать этот оператор.
Помните 12.8:
Пользовательский оператор назначения копирования X:: operator = является нестационарным не-шаблонная функция класса X с точно одним параметром тип X, X &, const X &, летучий X & или const volatile X &.) [Примечание: перегруженный оператор присваивания должен быть объявлен только одним параметр; см. 13.5.3. ] [Примечание: более одной формы назначения копии оператор может быть объявлен для класса. ] [Примечание: если класс X имеет только оператор присваивания копии с параметром типа X &, выражение тип const X не может быть назначен объекту типа X.
EDIT не делайте этого (вы можете понять почему?):
Вы можете сделать:
template<typename Derived>
struct copy_and_swap
{
void operator=(const copy_and_swap& copy)
{
Derived copy(static_cast<const Derived&>(copy));
copy.swap(static_cast<Derived&>(*this));
}
};
но вы теряете потенциальную оптимизацию при копировании.
В самом деле, это будет дважды назначать члены производных классов: один раз через copy_and_swap<Derived>
оператор присваивания и один раз через сгенерированный оператор присваивания производного класса. Чтобы исправить ситуацию, вам придется делать (и не забывать делать):
struct Foo : copy_and_swap<Foo>
{
Foo& operator=(const Foo& x)
{
static_cast<copy_and_swap<Foo>&>(*this) = x;
return *this;
}
private:
// Some stateful members here
}
Мораль истории: не пишите класс CRTP для идиомы копирования и подкачки.
Ответ 3
Вы не можете наследовать операции присваивания как особый случай, если память правильно обслуживается. Я считаю, что они могут быть явно using
'd, если вам нужно.
Кроме того, будьте осторожны при использовании копирования и замены. Он создает неидеальные результаты, когда у оригинала есть ресурсы, которые можно было бы повторно использовать для создания копии, например, контейнеров. Безопасность гарантирована, но оптимальная производительность - нет.
Ответ 4
Компилятор автоматически создает оператор присваивания копии для Foo, поскольку его нет.
Если вы добавите
using copy_and_swap<Foo>::operator=;
to Foo вы увидите ошибку, сообщающую вам о двусмысленности в g++.
Ответ 5
Возможно, вы могли бы переписать его так, чтобы он выглядел так:
template<class Derived>
struct CopySwap
{
Dervied &operator=(Derived const &other)
{
return AssignImpl(other);
}
Derived &operator=(Dervied &&other)
{
return AssignImpl(std::move(other));
}
private:
Derived &AssignImpl(Derived other)
{
auto self(static_cast<Derived*>(this));
self->swap(other);
return *self;
}
};
Скорее всего, все получится в строгом соответствии и, скорее всего, не будет медленнее исходного кода.
Ответ 6
Это не отвечает на вопрос (@Alexandre C. уже сделал), но если вы отмените наследование, вы можете заставить его работать:
template<typename Base>
struct copy_and_swap : Base
{
copy_and_swap& operator=(copy_and_swap copy)
{
swap(copy);
return *this;
}
};
struct Foo_
{
Foo_()
{
std::cout << "default\n";
}
Foo_(const Foo_& other)
{
std::cout << "copy\n";
}
void swap(Foo_& other)
{
std::cout << "swap\n";
}
};
typedef copy_and_swap<Foo_> Foo;
int main()
{
Foo x;
Foo y;
x = y;
}