6

Я недавно прочитал (и, к сожалению, забыл где), что лучший способ, чтобы написать оператор = как это:явный конструктор копирования или неявный параметр по значению

foo &operator=(foo other) 
{ 
    swap(*this, other); 
    return *this; 
} 

вместо этого:

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

Идея состоит в том, что если оператор = вызывается с rvalue, первая версия может оптимизировать построение копии. Поэтому, когда вызывается с rvalue, первая версия выполняется быстрее, а при вызове с lvalue два эквивалентны.

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

+0

Что такое 'swap'? Если это 'foo temp = x; х = у; y = temp; 'у вас есть infinte рекурсия функции' operator = 'и' swap'. –

+1

Я написал программу для проверки теории @ Алексея Малистова, и он был прав - я получил бесконечную рекурсию. – nobar

+0

Любой класс, который реализует идиому копирования и подкачки, не может полагаться на реализацию 'std :: swap' по умолчанию в своем операторе присваивания копий. Это почти само собой разумеется. –

ответ

4

Вы, наверное, читали его от: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Я не так много сказать, так как я думаю, что ссылка мотивировка довольно хорошо. Анекдотически я могу подтвердить, что первая форма приводит к уменьшению количества копий в моих сборках с помощью MSVC, что имеет смысл, поскольку компиляторы могут не выполнять копирование по второй форме. Я согласен с тем, что первая форма является строгим улучшением и никогда не бывает хуже второго.

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

Редактировать # 2: Ой, я хотел написать копию, а не RVO.

+1

Я не согласен с тем, что первый из них идиоматичен. Это менее распространенная версия. Я думаю, что если вы работаете в большой компании, которая проведет много обзоров кода, вы обнаружите, что вас отправили обратно, чтобы сделать это обычным способом. Нормальный лучше для обслуживания, так как людям не нужно делать двойной прием, чтобы понять, что происходит. Я думаю, что первая версия - это просто приятный партийный трюк, чтобы показать, насколько вы круты. –

+1

Я не совсем сказал, что первая форма была идиоматической (и я сказал, что вторая форма была более). Но идиомы должны начинаться где-то, и я знаю, что вторая форма заставила меня сделать двойную съемку в первый раз, когда я это увидел. – jamesdlin

+1

@ Мартин: как сказал Траб Саттер, когда конвенция противоречит хорошей практике, мы должны спросить себя, следует ли отказаться от конвенции в пользу хорошей практики или отказаться от хорошей практики в пользу конвенции. –

-3

Я думаю, что вы могли бы ввести в заблуждение разницы между:

foo &operator=(const foo &other); и
const foo &operator=(const foo &other);

Первой формой должны быть использованы для обеспечения: (a = b) = c;

+4

Я не думаю, что он вводит в заблуждение тех, - его вопрос имеет смысл. – 2010-01-09 19:48:15

-2

Этих два фактически то же самое. Единственное различие заключается в том, что вы нажимаете «Step In» в отладчике. И вы должны заранее знать, где это сделать.

2

Я предпочитаю второй вариант с точки зрения удобочитаемости и наименее неожиданности, однако я подтверждаю, что первый из них может быть более эффективным, когда параметр является временным.

Первый действительно может привести к копиям no, а не только к одному экземпляру, и вполне возможно, что это может быть подлинной проблемой в экстремальных ситуациях.

E.g. Возьмите эту тестовую программу. gcc -O3 -S (gcc версия 4.4.2 20091222 (Red Hat 4.4.2-20) (GCC)) генерирует один вызов конструктору копии B, но не вызывает конструктор копирования A для функции f (оператор присваивания является строчным как для A, так и для B). A и B могут быть взяты как очень простые классы строк. Распределение и копирование для data произойдет в конструкторах и освободится в деструкторе.

#include <algorithm> 

class A 
{ 
public: 
    explicit A(const char*); 
    A& operator=(A val)  { swap(val); return *this; } 
    void swap(A& other)  { std::swap(data, other.data); } 
    A(const A&); 
    ~A(); 

private: 
    const char* data; 
}; 

class B 
{ 
public: 
    explicit B(const char*); 
    B& operator=(const B& val) { B tmp(val); swap(tmp); return *this; } 
    void swap(B& other)   { std::swap(data, other.data); } 
    B(const B&); 
    ~B(); 

private: 
    const char* data; 
}; 

void f(A& a, B& b) 
{ 
    a = A("Hello"); 
    b = B("World"); 
} 
+0

Заметно, что ни один из классов не имеет элементов данных. Кроме того, вы могли бы изменить свой пример, чтобы немного свести к минимуму вертикальное пустое пространство? – 2010-01-09 20:08:29

+0

@Neil: Элементы данных не имеют значения, я просто добавил «длинные данные [32];» для обеих структур в моей тестовой жгуте. Теоретически член данных может быть затронут конструкторами, которые я оставил неопределенными. Вызовы, сгенерированные конструкторами, остаются неизменными, один вызов 'B :: B (const B &)', нулевые вызовы на 'A :: A (const A &)'. –

+0

А, я думаю, я понимаю, что вы имеете в виду. Я добавил еще одного члена класса класса (класс C) с объявленным, но неопределенным конструктором, конструктором копирования, деструктором и элементом данных void *. Несмотря на наличие некоторого дополнительного кода обработки исключений, и некоторые вызовы 'C :: ~ C()' в ожидаемых местах, разница между вызовом 'B :: B (const B &)' и 'A :: A (const &) '. Независимо от того, делает ли это разницу в реальном мире открытой для обсуждения, но, безусловно, есть вероятность. –

0

Учитывая это

foo &foo::operator=(foo other) {/*...*/ return *this;} 
foo f(); 

в коде как этот

foo bar; 
bar = f(); 

может быть проще для компилятора, чтобы исключить вызов конструктора копирования. В RVO он может использовать адрес параметра other оператора в качестве места для f() для построения его возвращаемого значения в.

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

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