Подтвердить что ты не робот

Преимущества функции свопинга?

Просматривая некоторые вопросы на С++, я часто замечал, что класс, совместимый с STL, должен реализовывать функцию swap (обычно как друг). Может ли кто-нибудь объяснить, какие преимущества это приносит, как STL вписывается в это и почему функция должна быть реализована как friend?

4b9b3361

Ответ 1

Для большинства классов обмен по умолчанию выполняется нормально, однако обмен по умолчанию не является оптимальным во всех случаях. Наиболее распространенным примером этого может быть класс с использованием Указатель на реализацию. Где, как и при обмене по умолчанию, большой объем памяти будет скопирован, вы специализированный swap, вы можете значительно ускорить его, только заменяя указатели.

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

Ответ 2

Стандартная версия std:: swap() будет работать для большинства типов, которые можно присваивать.

void std::swap(T& lhs,T& rhs)
{
    T tmp(lhs);
    lhs = rhs;
    rhs = tmp;
} 

Но это не оптимальная реализация, поскольку она вызывает вызов конструктора копирования, за которым следуют два вызова оператора присваивания.

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

Например std::vector. Реализация по умолчанию, определенная выше, была бы очень дорогостоящей, поскольку вам нужно было бы сделать копию всей области данных. Потенциально отпустите старые области данных или перераспределите область данных, а также вызовите конструктор копирования для содержащегося типа для каждого скопированного элемента. Специализированная версия имеет простой простой способ сделать std:: swap()

// NOTE this is not real code.
// It is just an example to show how much more effecient swaping a vector could
// be. And how using a temporary for the vector object is not required.
std::swap(std::vector<T>& lhs,std::vector<T>& rhs)
{
    std::swap(lhs.data,rhs.data);  // swap a pointer to the data area
    std::swap(lhs.size,rhs.size);  // swap a couple of integers with size info.
    std::swap(lhs.resv,rhs.resv);
}

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

Лично мне нравится реализовать swap() как метод небрасывающего члена. Затем укажите специализированную версию std:: swap():

class X
{
    public:
        // As a side Note:
        //    This is also useful for any non trivial class
        //    Allows the implementation of the assignment operator
        //    using the copy swap idiom.
        void swap(X& rhs) throw (); // No throw exception guarantee
};


  // Should be in the same namespace as X.
  // This will allows ADL to find the correct swap when used by objects/functions in
  // other namespaces.
  void swap(X& lhs,X& rhs)
  {
     lhs.swap(rhs);
  } 

Ответ 3

Если вы хотите обменять (например) два вектора, ничего не зная о их реализации, вам в основном нужно сделать что-то вроде этого:

typedef std::vector<int> vec;

void myswap(vec &a, vec &b) {
   vec tmp = a;
   a = b;
   b = tmp;
}

Это неэффективно, если a и b содержат много элементов, поскольку все эти элементы копируются между a, b и tmp.

Но если функция обмена будет знать и иметь доступ к внутренним элементам вектора, возможно, будет более эффективная реализация:

void std::swap(vec &a, vec &b) {
   // assuming the elements of the vector are actually stored in some memory area
   // pointed to by vec::data
   void *tmp = a.data;
   a.data = b.data;
   b.data = tmp;
   // ...
}

В этой реализации нужно скопировать несколько указателей, а не все элементы, как в первой версии. И поскольку для этой реализации требуется доступ к внутренним элементам вектора, она должна быть функцией друга.

Ответ 4

Еще одно использование функции свопинга заключается в том, чтобы помочь исключающему код: http://www.gotw.ca/gotw/059.htm

Ответ 5

Я интерпретировал ваш вопрос как принципиально три разных (связанных) вопроса.

  • Зачем нужна замена STL?
  • Почему должен быть реализован специализированный своп (i.s.o., полагающийся на значение по умолчанию swap)?
  • Почему это должно быть реализовано как друг?

Почему STL нуждается в свопинге?

Причина, по которой класс STL-friendly нуждается в swap, заключается в том, что swap используется как примитивная операция во многих алгоритмах STL. (например, reverse, sort, partition и т.д., как правило, реализуются с использованием swap)

Почему должен быть реализован специализированный swap (i.s.o., полагающийся на значение по умолчанию swap)?

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

Почему это должно быть реализовано как друг?

Алгоритмы STL всегда будут вызывать swap как свободную функцию. Поэтому он должен быть доступен как функция, не являющаяся членом, чтобы быть полезной. И, поскольку полезно создавать индивидуальный своп, когда вы можете использовать знание внутренних структур для написания гораздо более эффективного обмена, это означает, что вашей бесплатной функции потребуется доступ к внутренним компонентам вашего класса, следовательно, к другу.

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

Обратите внимание, что вы должны убедиться, что свободная функция находится в том же пространстве имен, что и ваш класс, так что алгоритмы STL могут найти вашу бесплатную функцию через поиск Koening.

Ответ 6

Эффективность:

Если у вас есть класс, который содержит (умные) указатели на данные, скорее всего, быстрее будет менять указатели, чем менять фактические данные - 3 указателя копий против 3 глубоких копий.

Если вы используете "using std:: swap" + неквалифицированный вызов для обмена (или просто квалифицированный вызов для boost:: swap), тогда ADL подберет пользовательскую функцию свопинга, позволяющую писать эффективный код шаблона,


Безопасность:

Пойнтерные свопы (raw-указатели, std:: auto_ptr и std:: tr1:: shared_ptr) не выбрасывают, поэтому их можно использовать для реализации не-бросающего свопа. Неперебрасываемый своп упрощает запись кода, который обеспечивает надежную гарантию исключения (транзакционный код).

Общая схема:

class MyClass 
{
  //other members etc...

  void method()
  {
    MyClass finalState(*this);//copy the current class
    finalState.f1();//a series of funcion calls that can modify the internal
    finalState.f2();//state of finalState and/or throw.
    finalState.f3();

    //this only gets call if no exception is thrown - so either the entire function 
    //completes, or no change is made to the object state at all.
    swap(*this,finalState);
  }
};

В отношении того, следует ли это реализовать как друга; для обмена обычно требуется знание деталей реализации. Это вопрос вкуса, следует ли использовать недруга, который вызывает функцию-член или использовать друга.


Проблемы:

Пользовательский своп часто быстрее, чем одно назначение, но одно назначение всегда быстрее, чем по умолчанию. Если вы хотите переместить объект, невозможно вообще узнать, будет ли своп или назначение лучше - проблема, которую С++ 0x решает с помощью конструкторов перемещения.

Ответ 7

Для реализации операторов присваивания:

class C
{
    C(C const&);
    void swap(C&) throw();
    C& operator=(C x) { this->swap(x); return *this; }
};

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