Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/130.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 什么时候重载按引用传递(l值和r值)比按值传递更可取?_C++_C++11_Overloading_Assignment Operator_Copy And Swap - Fatal编程技术网

C++ 什么时候重载按引用传递(l值和r值)比按值传递更可取?

C++ 什么时候重载按引用传递(l值和r值)比按值传递更可取?,c++,c++11,overloading,assignment-operator,copy-and-swap,C++,C++11,Overloading,Assignment Operator,Copy And Swap,我看到有人说过,在C++11中,编写一个操作符=以按值获取相同类型的参数,既可以作为复制赋值操作符,也可以作为移动赋值操作符: Foo& operator=(Foo f) { swap(f); return *this; } 如果备选方案的行数是原来的两倍多,代码重复次数多,并且可能出现错误: Foo& operator=(const Foo& f) { Foo f2(f); swap(f2); return *this; }

我看到有人说过,在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;
}
在什么情况下,引用常量和r值重载比 按值传递,或何时需要?我在考虑std::vector::push_back, 例如,定义为两个重载:

void push_back (const value_type& val);
void push_back (value_type&& val);
下面是第一个示例,其中传递值用作副本赋值 运算符和移动分配运算符,无法在中定义 标准是一个单一的功能吗

void push_back (value_type val);

对于副本分配运算符可以回收资源的类型,使用副本交换几乎从来都不是实现副本分配运算符的最佳方式。例如,查看
std::vector

此类管理动态大小的缓冲区,并维护
容量(缓冲区可容纳的最大长度)和
大小(当前长度)。如果实现了
向量
复制分配运算符
交换
,则无论发生什么情况,如果
rhs.size()!=0

但是,如果
lhs.capacity()>=rhs.size()
,则根本不需要分配新的缓冲区。可以简单地将元素从
rhs
分配/构造到
lhs
。当元素类型是可复制的时,这可能归结为除了
memcpy
之外什么都没有。这可能比分配和释放缓冲区快得多

std::string
也有同样的问题

MyType
的数据成员为
std::vector
和/或
std::string
时,
MyType
也存在同样的问题

只有2次你想考虑用SWAP实现拷贝分配:

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

  • 您知道,您将始终需要复制分配操作员具有强大的异常安全保证

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

  • 无例外的交换
  • noexcept移动赋值运算符
  • 例如:

    template <class T>
    T&
    strong_assign(T& x, T y)
    {
        using std::swap;
        swap(x, y);
        return x;
    }
    
    想象一下向量
    ,其中:

    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_类型
    放入
    向量
    需要2份而不是1份,即使
    容量足够。就性能而言,这将是一场灾难

    更新

    这是一个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";
    }
    
    通过这个简单的测试,我看到复制/交换习惯用法的性能提高了43%。YMMV

    平均而言,上述测试有一半时间在lhs上具有足够的容量。如果我们把这一点推向极端:

  • lhs始终具有足够的容量
  • lhs在任何时候都没有足够的容量
  • 那么默认拷贝分配相对于拷贝/交换习惯用法的性能优势从560%到0%不等。复制/交换习惯用法的速度永远不会更快,而且可能会显著减慢(对于本测试)


    想要速度吗?测量。

    当对象很难移动(例如,包含大量数据成员)时,按值形式可能更昂贵(两次移动而不是一次)。这可能部分是历史原因,因为
    std::vector
    在C++03中已经有
    T const&
    重载。可能存在依赖于现有重载的代码(比如有人获取了成员函数的地址)。还请注意,在该标准中,不需要优化必须实现的代码行,因为它由编译器实现者编写一次,但在其他地方几乎都使用。开发的额外成本可以忽略不计。出于明显的原因,复制/移动构造函数是绝对必要的一种情况。复制省略会有帮助吗?在第二种情况下,这会删除另一个副本吗?我想说的是,当rhs是左值时,ThanksIt没有帮助。从技术上讲,那篇文章没有错。然而,这给人留下了错误的印象:总是按价值复制是可怕的建议。就这一点而言,“任何事”都是可怕的建议。这篇文章应该给您留下这样的印象:有时候,一个by-value参数是可以的,甚至是最好的。但是太多的人忽视了“有时”,因为文章没有阐明这一点。在工具箱中保留传递值。但是在默认情况下,如果不进行设计,使用它会带来性能问题。请注意,@HowardHinnant的
    push\u back(const-value\u-type&)
    示例很难正确编写,因为参数可能是一个元素,或者更糟的是,它属于向量的一个元素。
    void push_back(value_type val);
    
    #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";
    }
    
    $ 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