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

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

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

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

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

Идея состоит в том, что если operator = вызывается с rvalue, первая версия может оптимизировать создание копии. Таким образом, при вызове с использованием значения r, первая версия быстрее, а при вызове с использованием значения l - эквивалентна.

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

5 ответов

Решение

Вы, вероятно, читаете это по адресу: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

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

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

Edit # 2: Ой, я хотел написать copy-elision, а не RVO.

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

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

Например, возьмите эту тестовую программу. 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");
}

Учитывая это

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

в таком коде

foo bar;
bar = f();

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

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

Эти два на самом деле одинаковы. Разница лишь в том, что вы нажимаете "Step In" в отладчике. И вы должны заранее знать, где это сделать.

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

foo &operator=(const foo &other); а также
const foo &operator=(const foo &other);

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

Другие вопросы по тегам