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

Компиляторы 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 ]

23 голосов
/ 17 января 2010

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

Причина, по которой нам пришлось сделать 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 rзначить функции, я не знаю. Я посмотрю это позже, или, может быть, кто-то другой может вмешаться, но это наверняка будет полезно. :)

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

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

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

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

11 голосов
/ 16 января 2010

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

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

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

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

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

2 голосов
/ 16 января 2010

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

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

1 голос
/ 16 января 2010

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

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

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

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

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

...