явный конструктор копирования или неявный параметр по значению - PullRequest
6 голосов
/ 09 января 2010

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

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

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

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

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

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

Ответы [ 5 ]

4 голосов
/ 09 января 2010

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

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

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

Редактировать # 2: Упс, я хотел написать copy-elision, а не RVO.

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

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

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

например. Возьми эту тестовую программу. 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 голосов
/ 09 января 2010

Учитывая это

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

в коде, подобном этому

foo bar;
bar = f();

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

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

0 голосов
/ 09 января 2010

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

0 голосов
/ 09 января 2010

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

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

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

...