在构造函数初始化列表上移动shared_ptr

Nie*_*eru 5 c++ optimization move move-semantics c++11

最近我看到了几个这样的代码示例,其中std :: move用于构造函数初始化列表(不是移动构造函数).

class A {
public:
    A(std::shared_ptr<Res> res) : myRes(std::move(res)) {
    // ...
    }

private:
    std::shared_ptr<Res> myRes;
}
Run Code Online (Sandbox Code Playgroud)

我得到的信息是这个结构是出于优化原因.我个人使用std :: move尽可能少见.我威胁他们作为演员(如Scott Meyers所说),并且只在调用者代码中(只有例外是移动构造函数).对我来说,它看起来像某种混淆或微观优化,但也许我错了.是不是真的,如果没有std :: move,编译器不会产生更快的代码?

Die*_*ühl 5

我认为缺少std::move()可移动的非平凡对象,但编译器无法检测到这是代码中的错误.也就是说,std::move()构造函数中的构造函数是强制性的:显然,构造函数调用的临时对象即将超出范围,即可以安全地移动它.另一方面,从参数构造成员变量不是可以省略副本的情况之一.也就是说,编译器必须创建一个副本,这个副本当然不是非常昂贵std::shared_ptr<T>但它也不是免费的.特别是,更新的引用计数需要同步.是否可以衡量差异是一个不同的问题.运行一个简单的基准测试(见下文)似乎意味着确实有性能改进.通常我得到的结果是这样的:

// clang:
copy: 440
move: 206
copy: 414
move: 209
// gcc:
copy: 361
move: 167
copy: 335
move: 170
Run Code Online (Sandbox Code Playgroud)

请注意,在此上下文中,您被调用成员的构造函数!这是正确的,std::move(res)只是一种编写演员的奇特方式(它是一个替代品static_cast<std::shared_ptr<RES>&&>(res)).但是,在对象即将超出范围但在其他情况下复制的地方使用是至关重要的.在语义上,std::move()在许多情况下使用是无关紧要的(在处理可移动但不可复制的类型时,它仅在语义上相关).避免不必要的副本是一项重要的性能改进,并且std::move()有助于在编译器无法推断出它可以或不被允许这样做的情况下这样做:特定情况是编译器甚至可能自己检测到的情况移动是安全的,但不允许通过移动替换副本.如果编译器会std::move()在这些情况下警告丢失,那将是很好的!

#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <iterator>
#include <memory>
#include <ostream>
#include <vector>

class timer
{
    typedef std::chrono::high_resolution_clock clock;
    clock::time_point d_start;
public:
    timer(): d_start(clock::now()) {}
    std::ostream& print(std::ostream& out) const {
        using namespace std::chrono;
        return out << duration_cast<microseconds>(clock::now() - this->d_start).count();
    }
};

std::ostream& operator<< (std::ostream& out, timer const& t)
{
    return t.print(out);
}

struct ResCopy
{
    std::shared_ptr<unsigned int> d_sp;
    ResCopy(std::shared_ptr<unsigned int> sp): d_sp(sp) {}
    unsigned int value() const { return *this->d_sp; }
};

struct ResMove
{
    std::shared_ptr<unsigned int> d_sp;
    ResMove(std::shared_ptr<unsigned int> sp): d_sp(std::move(sp)) {}
    unsigned int value() const { return *this->d_sp; }
};

template <typename Res>
void measure(char const* name, std::vector<std::shared_ptr<unsigned int>> const& v)
{
    timer t;
    unsigned long value(0);
    for (int c(0); c != 100; ++c) {
        for (std::size_t i(0), end(v.size()); i != end; ++i) { 
            value += Res(v[i]).value();
        }
    }
    std::cout << name << ": " << t << '\n';
}

int main()
{
    std::vector<std::shared_ptr<unsigned int>> v;
    std::generate_n(std::back_inserter(v), 100,
                    []{ return std::shared_ptr<unsigned int>(new unsigned int(std::rand())); });

    measure<ResCopy>("copy", v);
    measure<ResMove>("move", v);
    measure<ResCopy>("copy", v);
    measure<ResMove>("move", v);
}
Run Code Online (Sandbox Code Playgroud)