Сдвиг-семантика оператора Unified Assignment С++

EDIT: разрешено смотреть комментарии - Не знаю, как отметить, как решить без ответа.

После просмотра видеоролика Channel 9 в семантике Perfect Forwarding/Move в С++ 0x я понял, что это хороший способ написать новые операторы присваивания.

#include <string>
#include <vector>
#include <iostream>

struct my_type 
{
    my_type(std::string name_)
            :    name(name_)
            {}

    my_type(const my_type&)=default;

    my_type(my_type&& other)
    {
            this->swap(other);
    }

    my_type &operator=(my_type other)
    {
            swap(other);
            return *this;
    }

    void swap(my_type &other)
    {
            name.swap(other.name);
    }

private:
    std::string name;
    void operator=(const my_type&)=delete;  
    void operator=(my_type&&)=delete;
};


int main()
{
    my_type t("hello world");
    my_type t1("foo bar");
    t=t1;
    t=std::move(t1);
}

Это должно допускать как r-значения, так и const & s для присвоения ему. Построив новый объект с соответствующим конструктором, а затем заменив содержимое на * this. Мне кажется, это звучит так, как будто никакие данные не копируются больше, чем нужно. А арифметика указателя дешева.

Однако мой компилятор не согласен. (g++ 4.6) И я получаю эту ошибку.

copyconsttest.cpp: In function ‘int main()’:
copyconsttest.cpp:40:4: error: ambiguous overload for ‘operator=’ in ‘t = t1’
copyconsttest.cpp:40:4: note: candidates are:
copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type)
copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted>
copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <near match>
copyconsttest.cpp:31:11: note:   no known conversion for argument 1 from ‘my_type’ to ‘my_type&&’
copyconsttest.cpp:41:16: error: ambiguous overload for ‘operator=’ in ‘t = std::move [with _Tp = my_type&, typename std::remove_reference< <template-parameter-1-1> >::type = my_type]((* & t1))’
copyconsttest.cpp:41:16: note: candidates are:
copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type)
copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted>
copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <deleted>

Я что-то делаю неправильно? Является ли эта плохая практика (я не думаю, что есть способ проверить, назначаете ли вы себя самостоятельно)? Является ли компилятор еще не готовым?

Спасибо

Ответы

Ответ 1

Будьте очень осторожны с идиомой присваивания копирования/свопа. Он может быть субоптимальным, особенно при применении без тщательного анализа. Даже если вам нужна сильная защита исключений для оператора присваивания, эта функциональность может быть получена иным образом.

В вашем примере я рекомендую:

struct my_type 
{
    my_type(std::string name_)
            :    name(std::move(name_))
            {}

    void swap(my_type &other)
    {
            name.swap(other.name);
    }

private:
    std::string name;
};

Это даст вам неявное копирование и перемещение семантики, которые пересылают в std::string копию и перемещение элементов. И автор std::string лучше знает, как выполнить эти операции.

Если ваш компилятор еще не поддерживает неявное поколение движений, но поддерживает специальные элементы по умолчанию, вы можете сделать это вместо:

struct my_type 
{
    my_type(std::string name_)
            :    name(std::move(name_))
            {}

    my_type(const mytype&) = default;
    my_type& operator=(const mytype&) = default;
    my_type(mytype&&) = default;
    my_type& operator=(mytype&&) = default;

    void swap(my_type &other)
    {
            name.swap(other.name);
    }

private:
    std::string name;
};

Вы также можете сделать это, если хотите просто указать свои специальные члены.

Если вы имеете дело с компилятором, который еще не поддерживает дефолтные специальные члены (или неявные элементы перемещения), то вы можете явно указать, что компилятор должен в конечном итоге по умолчанию, когда он станет полностью совместимым с С++ 11:

struct my_type 
{
    my_type(std::string name_)
            :    name(std::move(name_))
            {}

    my_type(const mytype& other)
        : name(other.name) {}
    my_type& operator=(const mytype& other)
    {
        name = other.name;
        return *this;
    }
    my_type(mytype&& other)
        : name(std::move(other.name)) {}
    my_type& operator=(mytype&& other)
    {
        name = std::move(other.name);
        return *this;
    }

    void swap(my_type &other)
    {
            name.swap(other.name);
    }

private:
    std::string name;
};

Если вам действительно нужна сильная защита исключений для назначения, спроектируйте его один раз и будьте откровенны (отредактируйте, чтобы включить предложение Люка Дантона):

template <class C>
typename std::enable_if
<
    std::is_nothrow_move_assignable<C>::value,
    C&
>::type
strong_assign(C& c, C other)
{
    c = std::move(other);
    return c;
}

template <class C>
typename std::enable_if
<
    !std::is_nothrow_move_assignable<C>::value,
    C&
>::type
strong_assign(C& c, C other)
{
    using std::swap;
    static_assert(std::is_nothrow_swappable_v<C>,  // C++17 only
                  "Not safe if you move other into this function");
    swap(c, other);
    return c;
}

Теперь ваши клиенты могут выбирать между эффективностью (мой тип:: operator =) или сильной безопасностью исключений с помощью strong_assign.

Ответ 2

Вы внимательно прочитали сообщение об ошибке? Он видит две ошибки: у вас есть несколько операторов присваивания копий и несколько операторов присваивания оператора. И это точно!

Специальные члены должны быть указаны не более одного раза, независимо от того, были ли они дефолты, удалены, условно определены или неявно обработаны, если их не учитывать. У вас есть два оператора присваивания копий (один принимает my_type, другой принимает my_type const &) и два оператора присваивания оператора (один принимает my_type, а другой принимает my_type &&). Обратите внимание, что оператор присваивания, который принимает my_type, может обрабатывать ссылки lvalue и rvalue, поэтому он действует как назначение копирования и перемещения.

Функциональная подпись большинства специальных членов имеет несколько форм. Вы должны выбрать один; вы не можете использовать необычный, а затем удалить обычный, потому что это будет двойное объявление. Компилятор автоматически использует необычно сформированный специальный элемент и не будет синтезировать специальный элемент с обычной подписью.

(Обратите внимание, что в этих ошибках упоминаются три кандидата. Для каждого типа присваивания он видит соответствующий удаленный метод, метод, который принимает my_type, а затем другой удаленный метод как аварийное приближение.)

Ответ 3

Предполагаете ли вы удалить эти перегрузки оператора присваивания? Разве ваше объявление оператора присваивания не должно быть шаблоном или чем-то еще? Я действительно не вижу, как это должно работать.

Обратите внимание, что даже если это сработало, путем реализации этого оператора переадресации, ресурсы, принадлежащие только что перенесенному объекту, будут освобождены в момент его окончания жизни, а не в точке назначения. Подробнее см. Здесь:

http://cpp-next.com/archive/2009/09/your-next-assignment/