Почему в C++ 0x не сгенерированы компилятором методы swap()?
Компиляторы C++ автоматически генерируют конструкторы копирования и операторы копирования-назначения. Почему бы и нет swap
тоже?
В наши дни предпочтительным методом реализации оператора присваивания копии является идиома копирования и замены:
T& operator=(const T& other)
{
T copy(other);
swap(copy);
return *this;
}
( игнорируя дружественную для копирования форму, которая использует передачу по значению).
Преимущество этой идиомы заключается в том, что она транзакционна перед лицом исключений (при условии, что swap
реализация не бросает). Напротив, сгенерированный компилятором оператор копирования-назначения по умолчанию рекурсивно выполняет назначение-копирование для всех базовых классов и элементов данных, и это не имеет таких же гарантий безопасности исключений.
Между тем, реализация swap
методы вручную утомительны и подвержены ошибкам:
- Чтобы убедиться, что
swap
не генерирует, он должен быть реализован для всех не POD членов в классе и в базовых классах, в их не POD членах и т. д. - Если сопровождающий добавляет новый элемент данных в класс, сопровождающий должен помнить об изменении этого класса.
swap
метод. Несоблюдение этого требования может привести к незначительным ошибкам. Кроме того, так какswap
это обычный метод, компиляторы (по крайней мере, я не знаю) не выдают предупреждения, еслиswap
реализация не завершена.
Не было бы лучше, если бы компилятор генерировал swap
методы автоматически? Тогда неявная реализация копирования-присвоения могла бы использовать ее.
Очевидный ответ, вероятно, таков: идиома копирования и обмена не существовала, когда разрабатывался C++, и теперь это может привести к поломке существующего кода.
Тем не менее, возможно, люди могли бы разрешить компилятору генерировать swap
используя тот же синтаксис, который использует C++0x для управления другими неявными функциями:
void swap() = default;
и тогда могут быть правила:
- Если есть сгенерированный компилятором
swap
метод, неявный оператор копирования-назначения может быть реализован с использованием копирования и замены. - Если нет сгенерированного компилятором
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-ссылки не просто применяются для обмена, оставляя нас с положительным компромиссом. (В целом более быстрый код, более чистый интерфейс, немного больше кода, не болееswap
ADL хлопот.)
Что касается того, можем ли мыdefault
Rvalue функции, я не знаю. Я посмотрю это позже, или, может быть, кто-то другой может вмешаться, но это наверняка будет полезно.:)
Тем не менее, имеет смысл разрешить default
Rvalue функции вместоswap
, Так что по сути, пока они позволяют= default
rvalue функции, ваш запрос уже сделан.:)
РЕДАКТИРОВАТЬ: я немного поиска, и предложение= 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
в таком случае? - поэтому мне было бы все равно, если объект, прежде чем они в конечном итоге уничтожены, останется в исходном состоянии или нет. Конечно, могут быть конкретные ситуации, когда вам это действительно может понадобиться, но я не понимаю, почему здесь следует отказаться от принципа "вы не платите за то, что не используете".