Повторное использование идиомы копирования и обмена
Я пытаюсь поместить идиому копирования и замены в многократно используемый миксин:
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;
Это только печатает "default" дважды, ни "copy", ни "swap" не печатаются. Что мне здесь не хватает?
6 ответов
Боюсь, что это одна из областей, где необходим макрос, из-за сложных правил для автоматически сгенерированных операторов копирования и присваивания.
Независимо от того, что вы делаете, вы находитесь в одном из двух случаев:
- Вы предоставили (явно) декларацию оператора присваивания, и в этом случае вы также должны предоставить определение
- Вы не предоставили (явно) объявление оператора присваивания, и в этом случае компилятор сгенерирует его, если у базовых классов и нестатических членов есть один доступный.
Поэтому возникает следующий вопрос: стоит ли автоматизировать такое написание?
Copy-And-Swap используется только для очень определенных классов. Я не думаю, что это того стоит.
Это:
Derived& operator=(Derived copy)
не объявляет оператор присваивания копии для базового класса (он имеет неправильную подпись). Таким образом, сгенерированный по умолчанию оператор присваивания в Foo
не будет использовать этот оператор.
Помните 12.8:
Объявленный пользователем оператор присваивания копии X::operator= - это нестатическая не шаблонная функция-член класса X с ровно одним параметром типа X, X&, const X&, volatile X& или const volatile X&.) [Примечание: перегруженный оператор присваивания должен иметь только один параметр; см. 13.5.3. ] [Примечание: для класса может быть объявлено более одной формы оператора копирования. ] [Примечание: если класс X имеет только оператор присваивания копии с параметром типа X &, выражение типа const X не может быть присвоено объекту типа X.
РЕДАКТИРОВАТЬ не делайте этого (понимаете почему?):
Ты можешь сделать:
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 для копии и меняйте идиомы.
Вы не можете наследовать операторы присваивания как особый случай, если память правильно обслуживает. Я считаю, что они могут быть явно using
бы, если вам нужно.
Кроме того, будьте осторожны при использовании копирования и замены. Это приводит к неидеальным результатам, когда оригинал имеет ресурсы, которые можно использовать повторно для создания копии, например контейнеры. Безопасность гарантирована, но оптимальной производительности нет.
Компилятор автоматически генерирует оператор присваивания для Foo, поскольку его нет. Если вы добавите
using copy_and_swap<Foo>::operator=;
to Foo вы увидите ошибку, сообщающую о неоднозначности в g++.
Может быть, вы могли бы переписать его так, чтобы он выглядел так:
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;
}
};
Вероятно, все это будет встроено и, вероятно, не будет медленнее, чем оригинальный код.
Это на самом деле не отвечает на вопрос ( @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;
}