7

我最近读到(不幸的是忘记了在哪里),写 operator= 的最佳方法是这样的:

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

而不是这个:

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

这个想法是,如果使用右值调用 operator=,第一个版本可以优化副本的构造。所以当用右值调用时,第一个版本更快,当用左值调用时,两者是等价的。

我很好奇其他人对此有何看法?人们会因为缺乏明确性而避免使用第一个版本吗?我是否正确,第一个版本可以更好并且永远不会更糟?

4

5 回答 5

4

您可能从以下网址阅读: http: //cpp-next.com/archive/2009/08/want-speed-pass-by-value/

我没有太多要说的,因为我认为该链接很好地解释了基本原理。有趣的是,我可以确认第一种形式导致我使用 MSVC 构建的副本更少,这是有道理的,因为编译器可能无法对第二种形式进行复制省略。我同意第一种形式是一种严格的改进,永远不会比第二种差。

编辑:第一种形式可能不那么惯用,但我认为它并不太清楚。(IMO,这并不比第一次看到赋值运算符的复制和交换实现更令人惊讶。)

编辑#2:糟糕,我的意思是写复制省略,而不是 RVO。

于 2010-01-09T20:07:02.747 回答
2

从可读性和“最少惊喜”的角度来看,我通常更喜欢第二个,但是我承认当参数是临时参数时,第一个可能更有效。

第一个确实会导致没有副本,而不仅仅是单个副本,可以想象这在极端情况下可能是一个真正的问题。

例如,参加这个测试程序。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");
}
于 2010-01-09T20:05:23.280 回答
0

鉴于这种

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

在这样的代码中

foo bar;
bar = f();

编译器可能更容易消除对复制构造函数的调用。使用 RVO,它可以使用运算符other参数的地址作为f()构造其返回值的位置。

对于第二种情况,这种优化似乎也是可能的,尽管我相信它可能更难。(特别是当操作符没有内联时。)

于 2010-01-09T20:05:55.057 回答
-2

这两个其实是一样的。唯一的区别是您在调试器中按“Step In”的位置。你应该提前知道在哪里做。

于 2010-01-09T19:55:59.693 回答
-3

我认为您可能会混淆以下之间的区别:

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

第一种形式应用于允许: (a = b) = c;

于 2010-01-09T19:46:39.780 回答