повторное использование идиомы копирования и обмена - PullRequest
11 голосов
/ 16 августа 2011

Я пытаюсь поместить идиому копирования и обмена в многократно используемый миксин:

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 ]

7 голосов
/ 16 августа 2011

This:

 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.

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для копирования и обмена идиома .

1 голос
/ 16 августа 2011

Вы не можете наследовать операторы присваивания как особый случай, если память правильно обслуживает.Я считаю, что они могут быть явно using ', если вам нужно.

Кроме того, будьте осторожны с чрезмерным использованием копирования и замены.Он дает неидеальные результаты, когда у оригинала есть ресурсы, которые можно использовать повторно для создания копии, например, контейнеры.Безопасность гарантирована, но оптимальной производительности нет.

0 голосов
/ 16 августа 2011

Это на самом деле не отвечает на вопрос ( @ Александр С. уже сделал ), но если вы отмените наследование, вы можете заставить его работать:

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;
}
0 голосов
/ 16 августа 2011

Боюсь, что это одна из областей, где необходим макрос, из-за сложных правил для автоматически сгенерированных операторов копирования и присваивания.

Независимо от того, что вы делаете, вы находитесь в одном из двух случаев:

  • Вы предоставили (явно) декларацию оператора присваивания, и в этом случае вы также должны предоставить определение
  • Вы не предоставили (явно) объявление оператора присваивания, и в этом случае компилятор сгенерирует его, если в базовых классах и нестатических членах есть один доступный.

Поэтому следующий вопрос: Стоит ли автоматизировать такое написание?

Copy-And-Swap используется только для очень определенных классов. Я не думаю, что оно того стоит.

0 голосов
/ 16 августа 2011

Может быть, вы могли бы переписать его так, чтобы он выглядел так:

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;
  }
};

Вероятно, все это будет встроено и, вероятно, не будет медленнее, чем оригинальный код.

0 голосов
/ 16 августа 2011

Компилятор автоматически генерирует оператор присваивания для Foo, поскольку его нет. Если вы добавите

    using copy_and_swap<Foo>::operator=;

to Foo вы увидите ошибку, сообщающую о неоднозначности в g ++.

...