显式复制构造函数或按值隐式参数

发布于 08-17 04:26 字数 453 浏览 8 评论 0原文

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

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

而不是这样:

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

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

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

I recently read (and unfortunately forgot where), that the best way to write operator= is like this:

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

instead of this:

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

The idea is that if operator= is called with an rvalue, the first version can optimize away construction of a copy. So when called with a rvalue, the first version is faster and when called with an lvalue the two are equivalent.

I'm curious as to what other people think about this? Would people avoid the first version because of lack of explicitness? Am I correct that the first version can be better and can never be worse?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

英雄似剑2024-08-24 04:26:31

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

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

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

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

You probably read it from: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

I don't have much to say since I think the link explains the rationale pretty well. Anecdotally I can confirm that the first form results in fewer copies in my builds with MSVC, which makes sense since compilers might not be able to do copy-elision on the second form. I agree that the first form is a strict improvement and is never worse than the second.

Edit:
The first form might be a bit less idiomatic, but I don't think it's much less clear. (IMO, it's not any more surprising than seeing the copy-and-swap implementation of the assignment operator for the first time.)

Edit #2: Oops, I meant to write copy-elision, not RVO.

旧时浪漫2024-08-24 04:26:31

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

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

例如,采用这个测试程序。 gcc -O3 -S(gcc 版本 4.4.2 20091222 (Red Hat 4.4.2-20) (GCC))生成一次对 B 的复制构造函数的调用,但不会调用 A 的函数复制构造函数 < code>f (AB 都内联赋值运算符)。 AB 都可以被视为非常基本的字符串类。数据的分配和复制将发生在构造函数中,而释放将发生在析构函数中。

#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");
}

I generally prefer the second one from readability and 'least surprise' point of view, however I do acknowledge that the first one can be more efficient when the parameter is a temporary.

The first one really can lead to no copies, not just the single copy and it's conceivable that this may be a genuine concern in extreme situations.

E.g. Take this test program. gcc -O3 -S (gcc version 4.4.2 20091222 (Red Hat 4.4.2-20) (GCC)) generates one call to B's copy constructor but no calls to A's copy constructor for the function f (the assignment operator is inlined for both A and B). A and B can both be taken to be very basic string classes. Allocation and copying for data would occur in the constructors and deallocation in the destructor.

#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");
}
寂寞清仓2024-08-24 04:26:31

考虑到这一点,

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

在这样的代码中

foo bar;
bar = f();

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

看来这种优化对于第二种情况也是可能的,尽管我认为这可能会更困难。 (特别是当运算符未内联时。)

Given this

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

in code like this

foo bar;
bar = f();

it might be easier for a compiler to eliminate the call to the copy constructor. With RVO, it could use the address of the operator's other parameter as the place for f() to construct its return value at.

It seems that this optimization is also possible for the second case, although I believe it might be harder. (Especially when the operator isn't inlined.)

请恋爱2024-08-24 04:26:31

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

These two are actually the same. The only difference is where you press "Step In" in a debugger. And you should know in advance where to do it.

心头的小情儿2024-08-24 04:26:31

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

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

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

I think that you might be confusing the difference between:

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

The first form should be used to allow for: (a = b) = c;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文