Что делает `std::swap`, когда применяется к этим объектам?

Код

using namespace std;

class A 
{
private:
  vector<int> a;
public:
  A(vector<int> x):a(x){}
  string toString()
  {
      string s;
      for (auto& element : a)
      {
          s += to_string(element) + " ";
      }
      return s;
  }
};

int main()
{
    A a1({1,2,3});
    A a2({11,12,13});

    cout << "a1 = " << a1.toString() << "\n";
    cout << "a2 = " << a2.toString() << "\n";
    
    swap(a1,a2);
    
    cout << "a1 = " << a1.toString() << "\n";
    cout << "a2 = " << a2.toString() << "\n";
    
    return 0;
}

результаты, как и ожидалось

a1 = 1 2 3                                                                                                            
a2 = 11 12 13                                                                                                         
a1 = 11 12 13                                                                                                         
a2 = 1 2 3 

Из cplusplus.com › std::swap в разделе сложность

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

Массив: линейный по N: выполняет операцию замены для каждого элемента.

Означает ли это, что std::swap при применении к a1 и a2 только меняет местами указатели на массивы [1,2,3] и [11,12,13], но не копирует int или что-то еще?

Что именно делает std::swap при применении к двум объектам класса A?

Предполагая, что std::swap копирует все элементы массива, должен ли я написать функцию static A::swap, используя vector::swap, временная сложность которой постоянна (из cplusplus.com › vector::swap) означает, что он меняет местами только указатели?


[..] хочу добавить примечание о том, что семантика std::swap изменена в C++17. Поэтому было бы неплохо упомянуть компилятор, его версию и стандарт, на который вы ориентируетесь.

Я надеялся, что простой вопрос не вызовет сложностей со стандартами C++ и версиями компиляторов. Обычно я компилирую свой код на C++11. Для полноты вот версия gcc на моем ноутбуке.

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/include/c++/4.2.1
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

person Remi.b    schedule 19.02.2018    source источник
comment
Я признаю поражение в вопросе перегрузки. Но затем хочу добавить примечание, что семантика std::swap изменена в C ++17. Поэтому было бы неплохо упомянуть компилятор, его версию и стандарт, на который вы ориентируетесь. И то, о чем вы спрашиваете, напрямую зависит от этого семантического изменения в C++17.   -  person Some programmer dude    schedule 19.02.2018


Ответы (2)


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

void swap(T &a, T &b) {
   T tmp{std::move(a)};
   a = std::move(b);
   b = std::move(tmp);
}

Для вашего примера класса он несколько раз вызовет операторы перемещения/перемещения по умолчанию (редактировать: из A), а они, в свою очередь, вызовут операторы из std::vector. IOW, вы можете ожидать, что ваша программа будет достаточно эффективной.

Кроме того, вы можете определить функцию swap, не являющуюся членом, в том же пространстве имен, что и A, и явно вызвать std::swap с векторными параметрами. Или позвоните напрямую std::vector::swap.

person chill    schedule 19.02.2018
comment
Следует отметить, что перемещение конструктивных и назначаемых частей появилось только в стандарте C++17. - person Some programmer dude; 19.02.2018
comment
@Someprogrammerdude Эти требования существуют до C++17. C++17 исключает эту функцию из разрешения перегрузки, если эти требования не выполняются. - person xskxzr; 19.02.2018

В зависимости от вашего компилятора и классов/типов, которые вы используете, функция подкачки либо копирует один из классов, либо использует конструктор/назначение перемещения (C++11 и выше) (http://en.cppreference.com/w/cpp/utility/move).

Ваш класс состоит только из вектора, и для векторных классов этот конструктор перемещения делает то, что вы называете просто "перестановкой указателей", поэтому это будет очень быстро. Просто имейте в виду, что реализация, например. конструктор копирования удалит неявно объявленный конструктор перемещения, который делает это «волшебство»!! (http://en.cppreference.com/w/cpp/language/move_constructor#Implicitly-declared_move_constructor)

Рассматривая функцию подкачки независимо друг от друга, я дам вам лавину дополнительных идей: одно из самых известных применений функции подкачки происходит в идиоме копирования и подкачки (https://progdoo.wordpress.com/2012/06/03/c11-copy-and-swap-idiom/). Наиболее эффективно это работает с реализованными конструкторами/назначениями перемещения.

person thogra    schedule 19.02.2018