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

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

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

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

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

(игнорирование формы, упрощающей копирование, которая использует значение pass-by-value).

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

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

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

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

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

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

void swap() = default;

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

  • Если существует сгенерированный компилятором метод swap, неявный оператор присваивания копий может быть реализован с использованием copy-and-swap.
  • Если не существует метода swap, сгенерированного компилятором, неявный оператор присваивания копий будет реализован по-прежнему (вызов copy-assigment для всех базовых классов и для всех членов).

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

4b9b3361

Ответ 1

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

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

Тем не менее, rvalue-ссылки полностью устраняют это. В С++ 0x swap реализуется как:

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

Это имеет гораздо больший смысл. Вместо копирования данных мы просто перемещаем данные. Это позволяет даже заменять не скопируемые типы, например потоки. В проекте стандарта С++ 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 просто поменяла бы указатель, но в некоторых случаях может быть неуклюжей. В С++ 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 сделал бы в первую очередь. Поэтому, хотя до С++ 0x я бы согласился с вашим предложением, пользовательские swap больше не нужны, с введением rvalue-ссылок. std::swap отлично работает в любом классе, который реализует функции перемещения.

На самом деле, я утверждаю, что реализация функции swap должна стать плохой практикой. Для любого класса, которому нужна функция swap, также нужны функции rvalue. Но в этом случае просто нет необходимости в беспорядке пользовательского swap. Размер кода увеличивается (две функции ravlue по сравнению с одним swap), но ссылки на rvalue не просто применяются для обмена, что дает нам положительный отклик. (Общий быстрый код, более чистый интерфейс, немного больше кода, не более swap ADL hassle.)

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

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

EDIT: я немного искал, и предложение для = default move было предложением n2583. Согласно this (который я не знаю, как читать очень хорошо), он был "возвращен". Он указан в разделе "Не готов для С++ 0x, но открыт для повторной отправки в будущем". Поэтому похоже, что он не будет частью С++ 0x, но может быть добавлен позже.

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

EDIT 2: Оглядевшись немного больше, я нашел это: Определение функций Move Special Member, которое намного более современно и выглядит так: мы можем по умолчанию move. Ура!

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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