Является ли специализация std::swap устаревшей, когда у нас есть семантика перемещения?

Возможный дубликат:
Переместить семантику == пользовательская функция подкачки устарела?

Вот как std::swap выглядит как в C++11:

template<typename T>
void swap(T& x, T& y)
{
  T z = std::move(x);
    x = std::move(y);
    y = std::move(z);
}

Нужно ли еще специализироваться std::swap для моих собственных типов, или будет std::swap быть настолько эффективным, насколько это возможно, при условии, что мой класс, конечно, определяет конструктор перемещения и оператор присваивания перемещения?

4 ответа

Решение

Специализация std::swap теперь необязательно, но не рекомендуется. Обоснование - производительность.

Для прототипирования кода, и, возможно, даже для большого кода доставки, std::swap будет достаточно быстро Однако, если вы находитесь в ситуации, когда вам нужно извлекать каждый кусочек из своего кода, написание пользовательского свопа все же может быть значительным преимуществом в производительности.

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

Перемещение конструктора: 1 загрузка и 2 магазина.

Назначение перемещения: 2 груза и 2 магазина.

Пользовательский обмен: 2 загрузки и 2 магазина.

std::swap 1 конструкция хода и 2 назначения перемещения, или: 5 нагрузок и 6 магазинов.

Пользовательский своп потенциально еще в два-три раза быстрее, чем std::swap, Хотя каждый раз, когда вы пытаетесь вычислить скорость чего-либо, подсчитывая нагрузки и запасы, оба будут быстро порочны.

Примечание. При расчете стоимости вашего задания на перемещение обязательно учитывайте, что вы будете переходить к значению перемещения из (в std::swap алгоритм). Это часто сводит на нет стоимость сделки, хотя и за счет филиала.

Специализируется std::swap не рекомендуется теперь, когда у нас есть семантика перемещения?

Нет. Это общая версия, но вы можете оптимизировать ее, чтобы пропустить операцию третьего перемещения. Я предпочитаю сочетать идиомы копирования и замены с настройкой std::swap для моих занятий.

Это означает, что у меня будет:

class Aaaa
{
public:
    Aaaa(); // not interesting; defined elsewhere
    Aaaa(Aaaa&& rvalueRef); // same
    Aaaa(const Aaaa& ref); // same
    ~Aaaa(); // same
    Aaaa& operator=(Aaaa object) // copy&swap
    {
        swap(object);
        return *this;
    }
    void swap(Aaaa& other)
    {
        std::swap(dataMember1, other.dataMember1);
        std::swap(dataMember2, other.dataMember2);
        // ...
    }

    // ...
};

namespace std
{
    template<> inline void std::swap(Aaaa& left, Aaaa& right)
    { left.swap(right); }
}

Это swap() вызывает конструктор перемещения и 2 назначения перемещения. Я думаю, что можно написать более эффективно swap() для его конкретного типа класса, как,

class X
{
    int * ptr_to_huge_array;
public:
// ctors, assgn ops. etc. etc.

    friend void swap(X& a, X& b)
    {
        using std::swap;
        swap(a.ptr_to_huge_array, b.ptr_to_huge_array);
    }
};

независимо от реализации конструктора перемещения и оператора присваивания.

Это будет зависеть от ваших типов.

Вы переместите его из x в z, из y в x, из z в y. Это три операции копирования базового представления (может быть, только один указатель, может быть, что-то еще, кто знает)

Теперь, возможно, вы можете создать более быстрый своп для вашего типа (трюк свопинга xor, встроенный ассемблер или, возможно, std::swap для ваших базовых типов просто быстрее).

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

Лично я склонен всегда реализовывать функцию-член подкачки, которая будет вызываться из нескольких мест, включая такие вещи, как назначение перемещения, но YMMV.

Другие вопросы по тегам