Почему в C++ 0x не сгенерированы компилятором методы swap()?

Компиляторы C++ автоматически генерируют конструкторы копирования и операторы копирования-назначения. Почему бы и нет swap тоже?

В наши дни предпочтительным методом реализации оператора присваивания копии является идиома копирования и замены:

T& operator=(const T& other)
{
    T copy(other);
    swap(copy);
    return *this;
}

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

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

Между тем, реализация swap методы вручную утомительны и подвержены ошибкам:

  1. Чтобы убедиться, что swap не генерирует, он должен быть реализован для всех не POD членов в классе и в базовых классах, в их не POD членах и т. д.
  2. Если сопровождающий добавляет новый элемент данных в класс, сопровождающий должен помнить об изменении этого класса. swap метод. Несоблюдение этого требования может привести к незначительным ошибкам. Кроме того, так как swap это обычный метод, компиляторы (по крайней мере, я не знаю) не выдают предупреждения, если swap реализация не завершена.

Не было бы лучше, если бы компилятор генерировал swap методы автоматически? Тогда неявная реализация копирования-присвоения могла бы использовать ее.

Очевидный ответ, вероятно, таков: идиома копирования и обмена не существовала, когда разрабатывался C++, и теперь это может привести к поломке существующего кода.

Тем не менее, возможно, люди могли бы разрешить компилятору генерировать swap используя тот же синтаксис, который использует C++0x для управления другими неявными функциями:

void swap() = default;

и тогда могут быть правила:

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

Кто-нибудь знает, были ли такие (безумные?) Предложения предложены комитету по стандартам C++, и если да, то какие мнения были у членов комитета?

4 ответа

Решение

Это в дополнение к ответу Терри.

Причина, по которой мы должны были сделать swap функции в C++ до 0x, потому что общая свободная функция std::swap был менее эффективным (и менее универсальным), чем мог бы быть. Он сделал копию параметра, затем имел два переназначения, а затем выпустил по существу потерянную копию. Создание копии тяжеловесного класса - пустая трата времени, когда мы, программисты, знаем, что все, что нам действительно нужно сделать, это поменять местами внутренние указатели и еще много чего.

Тем не менее, rvalue-ссылки облегчают это полностью. В C++0x swap реализован как:

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

Это имеет гораздо больше смысла. Вместо того, чтобы копировать данные, мы просто перемещаем данные. Это позволяет даже не копируемые типы, такие как потоки, обмениваться. В проекте стандарта C++0x говорится, что для замены типов на std::swap, они должны быть конструируемыми rvalue и назначаемыми rvalue (очевидно).

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

struct dumb_vector
{
    int* pi; // lots of allocated ints

    // constructors, copy-constructors, move-constructors
    // copy-assignment, move-assignment
};

Ранее, swap сделал бы избыточную копию всех наших данных, прежде чем отбросить их позже. Наш обычай swap Функция просто поменяет указатель, но в некоторых случаях может быть неудобной для использования. В C++0x перемещение достигает того же конечного результата. призвание std::swap будет генерировать:

dumb_vector temp(std::move(x));
x = std::move(y);
y = std::move(temp);

Что переводится как:

dumb_vector temp;
temp.pi = x.pi; x.pi = 0; // temp(std::move(x));
x.pi = y.pi; y.pi = 0; // x = std::move(y);
y.pi = temp.pi; temp.pi = 0; // y = std::move(temp);

Компилятор, конечно, избавится от избыточных присваиваний, оставив:

int* temp = x.pi;
x.pi = y.pi;
y.pi = temp;

Что именно то , что наш обычай swap сделал бы в первую очередь. Так что пока до C++0x я бы согласился с вашим предложением, кастомswapбольше не нужны, с введением rvalue-ссылок.std::swapбудет отлично работать в любом классе, который реализует функции перемещения.

На самом деле, я бы сказал, что реализация swapфункция должна стать плохой практикой. Любой класс, который будет нуждаться в swapФункция также должна была бы использовать функции rvalue. Но в этом случае просто нет необходимости в беспорядке обычая swap, Размер кода увеличивается (две функции ravlue против однойswap), но rvalue-ссылки не просто применяются для обмена, оставляя нас с положительным компромиссом. (В целом более быстрый код, более чистый интерфейс, немного больше кода, не болееswapADL хлопот.)

Что касается того, можем ли мыdefaultRvalue функции, я не знаю. Я посмотрю это позже, или, может быть, кто-то другой может вмешаться, но это наверняка будет полезно.:)

Тем не менее, имеет смысл разрешить default Rvalue функции вместоswap, Так что по сути, пока они позволяют= defaultrvalue функции, ваш запрос уже сделан.:)

РЕДАКТИРОВАТЬ: я немного поиска, и предложение= defaultход был предложениемn2583, Согласно этому (который я не знаю, как читать очень хорошо), это было "перемещено назад". Он указан в разделе "Не готов к C++0x, но открыт для повторной отправки в будущем". Похоже, он не будет частью C++0x, но может быть добавлен позже.

Несколько разочаровывает.:(

РЕДАКТИРОВАТЬ 2: оглянувшись немного поподробнее, я обнаружил, что это: Определение специальных функций Move, которое намного новее и похоже, что мы можем по умолчанию move, Ура!

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

Вы должны специализировать std::swap (в пространстве имен рядом с вашим UDT, так что он найден ADL), если вы можете добиться большего. Это идиоматично - просто отложить его до функции обмена членами.

В то время как мы находимся на предмете, это также идиоматично в C++0x (насколько возможно иметь идиомы на таком новом стандарте), чтобы реализовать конструкторы rvalue как своп.

И да, в мире, где обмен членов был языковым дизайном, а не свободным обменом функций, это означало бы, что нам нужен оператор обмена вместо функции - иначе примитивные типы (int, float и т. Д.) Не могли бы не должны рассматриваться вообще (так как у них нет функции-члена swap). Так почему же они этого не сделали? Вы должны были бы спросить членов комитета наверняка - но я на 95% уверен, что причина в том, что комитет давно предпочитает библиотечные реализации функций, когда это возможно, вместо изобретения нового синтаксиса для реализации функции. Синтаксис оператора подкачки был бы странным, потому что в отличие от =, +, - и т. Д., И всех других операторов, не существует алгебраического оператора, с которым все знакомы для "своп".

С ++ синтаксически достаточно сложен. Они идут на все, чтобы не добавлять новые ключевые слова или синтаксические возможности, когда это возможно, и делают это только по очень веским причинам (лямбда-выражения!).

Кто-нибудь знает, были ли такие (сумасшедшие?) Вещи предложены комитету по стандартам C++?

Отправить письмо Бьярне. Он знает все это и обычно отвечает в течение нескольких часов.

Планируется ли даже сгенерированный компилятором конструктор / назначение перемещения (с ключевым словом по умолчанию)?

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

Даже при том, что идиома оставляет объект неизменным в случае исключений, разве эта идиома, требуя создания третьего объекта, не увеличивает шансы на провал в первую очередь?

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

Как правило, я не перегружаю оператор = и не беспокоюсь об этом уровне безопасности исключений: я не заключаю отдельные назначения в блоки try - что бы я сделал с наиболее вероятной std::bad_alloc в таком случае? - поэтому мне было бы все равно, если объект, прежде чем они в конечном итоге уничтожены, останется в исходном состоянии или нет. Конечно, могут быть конкретные ситуации, когда вам это действительно может понадобиться, но я не понимаю, почему здесь следует отказаться от принципа "вы не платите за то, что не используете".

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