繁体   English   中英

何时重载通过引用(l值和r值)优先传递给值?

[英]When is overloading pass by reference (l-value and r-value) preferred to pass-by-value?

我已经看到它说一个operator=写入相同类型的参数by-value既作为C ++ 11中的复制赋值运算符又是移动赋值运算符:

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

替代方案的重复次数将超过两倍,并且可能出现错误:

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 ,例如它被定义为两个重载:

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

在第一个示例中,按值传递作为复制赋值运算符和移动赋值运算符 ,不能将标准中的push_back定义为单个函数吗?

void push_back (value_type val);

对于复制赋值运算符可以回收资源的类型,使用副本进行交换几乎不是实现复制赋值运算符的最佳方法。 例如,看看std::vector

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

但是,如果lhs.capacity() >= rhs.size() ,则根本不需要分配新的缓冲区。 人们可以简单地分配/构造从rhslhs的元素。 当元素类型可以轻易地复制时,这可以归结为memcpy 这可能是多少,比分配和释放缓冲区快得多

std::string问题相同。

同样的问题,为MyTypeMyType有数据成员是std::vector和/或std::string

您只想考虑使用swap实现复制分配的次数:

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

  2. 您知道,您始终需要复制赋值运算符才能获得强大的异常安全保证。

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

  1. 一个noexcept交换。
  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;
}

现在有一些类型,使用swap实现复制赋值是有意义的。 但是这些类型将是例外,而不是规则。

上:

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

想象一下vector<big_legacy_type>其中:

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

如果我们只有:

void push_back(value_type val);

然后将一个左值big_legacy_type push_back到一个vector需要2个副本而不是1个,即使capacity足够。 这将是一场灾难,表现明智。

更新

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

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

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

  1. 隐含地,这相当于调用vector的复制赋值运算符。
  2. 用复制/交换习语,暗示在宏SLOW_DOWN 我考虑过将其命名为SLEEP_FOR_AWHILE ,但如果您使用的是电池供电的设备,这种方式实际上比睡眠语句更糟糕。

该测试构造一些0到1000之间的随机大小的vector<int> ,并为它们分配一百万次。 它计算每一个,对时间求和,然后找到浮点纳秒的平均时间并打印出来。 如果对高分辨率时钟的两次连续调用未返回小于100纳秒的内容,则可能需要提高向量的长度。

这是我的结果:

$ 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%。 因人而异。

平均而言,上述测试在lhs的一半时间内具有足够的容量。 如果我们把它带到极端:

  1. lhs一直有足够的容量。
  2. lhs没有足够的容量。

那么默认拷贝分配相对于拷贝/交换习语的性能优势从大约560%到0%不等。 复制/交换习惯用法永远不会更快,并且速度可能会慢得多(对于此测试)。

想要速度? 测量。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM