2010-01-16 3 views
41

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

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

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

(ignoring the copy-elision-friendly form that uses pass-by-value).

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

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

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

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

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

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

void swap() = default; 

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

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

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

+0

Я задавался одно и то же количество.! раз, тем более, что 'swap' обычно реализуется в терминах' swapping' всех элементов данных в любом случае, так что это похоже на 'copy' или' assign' и новые версии 'move'. –

+7

В C++ 0x' swap 'должно быть чем-то вроде строки' template void swap (T & x, T & y) {T temp (std :: move (x)); x = std :: move (y); y = std :: move (temp);} ', который по существу будет делать то, что сделают любые пользовательские функции свопинга. Я согласен с вашим вопросом, но пользовательские свопы больше не нужны, с введением ссылки на значения r-значения. 'std :: swap' будет работать отлично. – GManNickG

+4

Я подозреваю, что причина, по которой это не в C++ 98, заключается в том, что единственные генерируемые компилятором функции в C++ существуют, чтобы заставить классы вести себя как C-структуры: вы можете объявить их, уничтожить и скопировать. Как бы полезен «swap» по умолчанию или оператор '' по умолчанию '' (и оба могут быть реализованы с помощью полевых операций), стандарт C++ требовал минимальных функций по умолчанию, поэтому есть меньше, чтобы подавить, если вы этого не хотите , Синтаксис C++ 0x 'function = something' может быть намного лучше, поскольку он является необязательным. В принципе, у вас могут быть разные варианты безопасного и небезопасного обмена. –

ответ

22

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

Причина, по которой мы должны были выполнить функции 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 hassle.)

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

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

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

Немного разочаровывает.:(

EDIT 2: Глядя вокруг немного больше, я нашел это: Defining Move Special Member Functions, который гораздо позднее, и будет выглядеть как мы можем по умолчанию move Yay

+0

Это имеет смысл. Я не совсем обернулся вокруг нового справочного материала rvalue и предположил, что это может быть фактором. Из того, что я читал, это похоже на то, что классы должны явно реализовать конструктор move, который будет иметь аналогичное бремя обслуживания, как реализация swap, если только не существует способа для выбора неявного определения с помощью '. .. = default'. Я полагаю, что даже если этого не происходит, по крайней мере, у компиляторов может быть легче генерировать предупреждения для неполных реализаций. – jamesdlin

+0

@james Yea, на самом деле больше нагрузки, так как вам нужен оператор move-move и оператор присваивания. Вероятно, хороший компромисс для всех других вещей, предоставляемых rvalue-ссылками. Надеюсь, мы сможем сделать функции r = value по умолчанию. – GManNickG

+0

Должны ли конструкторы и назначения rvalue быть nothrow, или это просто настоятельно рекомендуется (чтобы получить nohrow 'std :: swap' для вашего класса)? Например, если у вас есть член класса legacy без rvalue ctor, тогда у вас может возникнуть соблазн написать rvalue ctor, который перемещает большую часть данных, но копирует этот бит. Это смерть для стандартной реализации swap, которую вы даете, это не обязательно даже было бы безопасным, не говоря уже о том, чтобы не дрогнуть. Поэтому, если я не пропустил что-то хитрое, вам нужно реализовать swap самостоятельно, чтобы называть своп на прежнем члене, плохую практику или нет. –

2

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

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

+5

Или, что лучше, поставите вопрос на Usenet на comp.std.C++, который является основной точкой общественного обсуждения для стандартных C++ и процессов стандартизации. – Richard

10

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

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

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

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

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

+4

UDT, ADL ... WTF? – ergosys

+22

пользовательский тип, зависимый от аргумента поиск, что f ** k –

+0

У меня нет такого впечатления. Я не говорю об автономной функции 'std :: swap' или о STL-контейнерах или алгоритмах вообще, просто предоставляя автоматические функции' swap'-члену для объектов, потому что компилятор проще делать это, чем люди. Тем не менее, я понимаю, что просто предоставление функции-члена не допускало бы согласованного синтаксиса, поэтому, если требуется согласованность, вероятно, потребуется, скажем, собственный оператор. – jamesdlin

1

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

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

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

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

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

+0

«вы не платите за то, что не используете». Если вы никогда не ловите исключений и никогда не пишете деструкторы, которые предполагают, что какие-либо объекты находятся в правильном состоянии, тогда вам не нужны гарантии исключений. Другие пользователи ваших классов иногда могут перехватывать исключения и делать что-то в деструкторах. Им нужны исключения. К сожалению, всегда невозможно предоставлять гарантии исключения, чтобы вы никогда не платили, если вы их не используете. К счастью, если ваш класс содержит ресурсы, а при назначении придется освобождать старый ресурс и выделять новый, то CAS не является дополнительным расходом. –

Смежные вопросы