什么时候重载传递引用(l 值和 r 值)优先于传递值? [英] When is overloading pass by reference (l-value and r-value) preferred to pass-by-value?

查看:28
本文介绍了什么时候重载传递引用(l 值和 r 值)优先于传递值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经看到它说一个 operator= 被写成一个相同类型的按值参数在 C++11 中同时用作复制赋值运算符和移动赋值运算符:

I have seen it said that a operator= written to take a parameter of the same type by-value serves as both copy assignment operator and move assignment operator in C++11:

Foo& operator=(Foo f)
{
    swap(f);
    return *this;
}

替代方案的行数是两倍多,代码重复很多,并且可能出错:

Where the alternative would be more than twice as many lines with a lot of code repetition, and potential for error:

Foo& operator=(const Foo& f)
{
    Foo f2(f);
    swap(f2);
    return *this;
}

Foo& operator=(Foo&& f)
{
    Foo f2(std::move(f));
    swap(f2);
    return *this;
}

在什么情况下,ref-to-const 和 r-value 重载优于按值传递,或者什么时候需要?我在考虑 std::vector::push_back,例如定义为两个重载:

In what circumstances is the ref-to-const and r-value overload preferable to pass by value, or when is it necessary? I'm thinking about std::vector::push_back, for example which is defined as two overloads:

void push_back (const value_type& val);
void push_back (value_type&& val);

遵循第一个示例,其中按值传递 用作复制分配运算符和移动赋值运算符,不能在push_back中定义标准是一个单一的功能?

Following the first example where pass by value serves as copy assignment operator and move assignment operator, couldn't push_back be defined in the Standard to be a single function?

void push_back (value_type val);

推荐答案

对于复制赋值运算符可以回收资源的类型,用副本交换几乎从来都不是实现复制赋值运算符的最佳方式.例如看 std::vector:

For types whose copy assignment operator can recycle resources, swapping with a copy is almost never the best way to implement the copy assignment operator. For example look at std::vector:

这个类管理一个动态大小的缓冲区并维护一个容量(缓冲区可以容纳的最大长度)和一个size(当前长度).如果vector 复制赋值运算符被实现swap,那么无论如何,如果rhs.size() != 0 总是分配一个新的缓冲区代码>.

This class manages a dynamically sized buffer and maintains both a capacity (maximum length the buffer can hold), and a size (the current length). If the vector copy assignment operator is implemented swap, then no matter what, a new buffer is always allocated if the rhs.size() != 0.

然而,如果lhs.capacity() >= rhs.size(),则根本不需要分配新的缓冲区.可以简单地分配/构造从 rhslhs 的元素.当元素类型可以简单地复制时,这可能归结为 memcpy.这比分配和取消分配缓冲区要快得多,.

However, if lhs.capacity() >= rhs.size(), no new buffer need be allocated at all. One can simply assign/construct the elements from rhs to lhs. When the element type is trivially copyable, this may boil down to nothing but memcpy. This can be much, much faster than allocating and deallocating a buffer.

std::string 同样的问题.

MyType 的数据成员为 std::vector 和/或 std::string<时,MyType 也有同样的问题/代码>.

Same issue for MyType when MyType has data members that are std::vector and/or std::string.

只有 2 次您想考虑使用交换实现复制分配:

There are only 2 times you want to consider implementing copy assignment with swap:

  1. 您知道 swap 方法(包括当 rhs 是左值时的强制复制构造)不会非常低效.

  1. You know that the swap method (including the obligatory copy construction when the rhs is an lvalue) will not be terribly inefficient.

您知道您将总是需要复制赋值运算符来拥有强大的异常安全保证.

You know that you will always need the copy assignment operator to have the strong exception safety guarantee.

如果您不确定 2,换句话说,您认为复制赋值运算符可能有时需要强大的异常安全保证,请不要在交换方面实现赋值.如果您提供以下之一,您的客户很容易获得相同的保证:

If you're not sure about 2, in other words you think the copy assignment operator might sometimes need the strong exception safety guarantee, don't implement assignment in terms of swap. It is easy for your clients to achieve the same guarantee if you provide one of:

  1. 无例外交换.
  2. 一个 noexcept 移动赋值运算符.

例如:

template <class T>
T&
strong_assign(T& x, T y)
{
    using std::swap;
    swap(x, y);
    return x;
}

或:

template <class T>
T&
strong_assign(T& x, T y)
{
    x = std::move(y);
    return x;
}

现在将有一些类型,使用交换实现复制分配是有意义的.然而,这些类型将是例外,而不是规则.

Now there will be some types where implementing copy assignment with swap will make sense. However these types will be the exception, not the rule.

开启:

void push_back(const value_type& val);
void push_back(value_type&& val);

想象一下 vector 其中:

class big_legacy_type
{
 public:
      big_legacy_type(const big_legacy_type&);  // expensive
      // no move members ...
};

如果我们只有:

void push_back(value_type val);

然后 push_back 将左值 big_legacy_type 放入 vector 将需要 2 个副本而不是 1 个,即使 capacity> 已经足够了.这将是一场灾难,性能明智.

Then push_backing an lvalue big_legacy_type into a vector would require 2 copies instead of 1, even when capacity was sufficient. That would be a disaster, performance wise.

更新

这是一个您应该能够在任何符合 C++11 的平台上运行的 HelloWorld:

Here is a HelloWorld that you should be able to run on any C++11 conforming platform:

#include <vector>
#include <random>
#include <chrono>
#include <iostream>

class X
{
    std::vector<int> v_;
public:
    explicit X(unsigned s) : v_(s) {}

#if SLOW_DOWN
    X(const X&) = default;
    X(X&&) = default;
    X& operator=(X x)
    {
        v_.swap(x.v_);
        return *this;
    }
#endif
};

std::mt19937_64 eng;
std::uniform_int_distribution<unsigned> size(0, 1000);

std::chrono::high_resolution_clock::duration
test(X& x, const X& y)
{
    auto t0 = std::chrono::high_resolution_clock::now();
    x = y;
    auto t1 = std::chrono::high_resolution_clock::now();
    return t1-t0;
}

int
main()
{
    const int N = 1000000;
    typedef std::chrono::duration<double, std::nano> nano;
    nano ns(0);
    for (int i = 0; i < N; ++i)
    {
        X x1(size(eng));
        X x2(size(eng));
        ns += test(x1, x2);
    }
    ns /= N;
    std::cout << ns.count() << "ns
";
}

我用两种方式编写了 X 的复制赋值运算符:

I've coded X's copy assignment operator two ways:

  1. 隐式,相当于调用vector的复制赋值运算符.
  2. 使用复制/交换习语,暗示在宏 SLOW_DOWN 下.我想把它命名为 SLEEP_FOR_AWHILE,但如果你使用的是电池供电的设备,这种方式实际上比睡眠语句糟糕得多.
  1. Implicitly, which is equivalent to calling vector's copy assignment operator.
  2. With the copy/swap idiom, suggestively under the macro SLOW_DOWN. I thought about naming it SLEEP_FOR_AWHILE, but this way is actually much worse than sleep statements if you're on a battery powered device.

该测试在 0 到 1000 之间构造了一些随机大小的 vector,并将它们分配了一百万次.它对每个时间进行计时,对时间求和,然后找到以浮点纳秒为单位的平均时间并将其打印出来.如果连续两次调用您的高分辨率时钟没有返回小于 100 纳秒的值,您可能需要增加向量的长度.

The test constructs some randomly sized vector<int>s between 0 and 1000, and assigns them a million times. It times each one, sums the times, and then finds the average time in floating point nanoseconds and prints that out. If two consecutive calls to your high resolution clock doesn't return something less than 100 nanoseconds, you may want to raise the length of the vectors.

这是我的结果:

$ clang++ -std=c++11 -stdlib=libc++ -O3 test.cpp
$ a.out
428.348ns
$ a.out
438.5ns
$ a.out
431.465ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DSLOW_DOWN test.cpp
$ a.out
617.045ns
$ a.out
616.964ns
$ a.out
618.808ns

通过这个简单的测试,我发现复制/交换习语的性能下降了 43%.天啊.

I'm seeing a 43% performance hit for the copy/swap idiom with this simple test. YMMV.

上述测试平均有一半的时间在 lhs 上有足够的容量.如果我们把它带到任何一个极端:

The above test, on average, has sufficient capacity on the lhs half the time. If we take this to either extreme:

  1. lhs 始终有足够的容量.
  2. lhs 从来没有足够的容量.

那么默认复制分配相对于复制/交换习语的性能优势从大约 560% 到 0% 不等.复制/交换习语永远不会更快,并且可能会显着变慢(对于此测试).

then the performance advantage of the default copy assignment over the copy/swap idiom varies from about 560% to 0%. The copy/swap idiom is never faster, and can be dramatically slower (for this test).

想要速度?测量.

这篇关于什么时候重载传递引用(l 值和 r 值)优先于传递值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆