我有一个琐碎的类,它的构造函数如下所示:

Event(std::function<void()> &&f) : m_f(std::move(f)) { }

构造函数可以与std::bind一起使用:
Thing thing;
std::unique_ptr<Event> ev(new Event(std::bind(some_func,thing)));

以上述方式使用它会导致“物”的一个拷贝构造,然后在该拷贝上进行移动构造。

但是,请执行以下操作:
std::unique_ptr<Event> ev = make_unique<Event>(std::bind(some_func,thing));

产生两个移动构造。我的问题是:
  • 什么时候将“thing”上的move构造函数称为
  • 为什么用make_unique调用两次?

  • 这是最小的示例:
    #include <iostream>
    #include <memory>
    #include <functional>
    using namespace std;
    
    class Thing
    {
    public:
        Thing() : x(0)
        {
    
        }
    
        Thing(Thing const &other)
        {
            this->x = other.x;
            std::cout << "Copy constructed Thing!\n";
        }
    
        Thing(Thing &&other)
        {
            this->x = other.x;
            std::cout << "Move constructed Thing!\n";
        }
    
        Thing & operator = (Thing const &other)
        {
            this->x = other.x;
            std::cout << "Copied Thing!\n";
            return (*this);
        }
    
        Thing & operator = (Thing && other)
        {
            this->x = other.x;
            std::cout << "Moved Thing!\n";
            return (*this);
        }
    
        int x;
    };
    
    class Event
    {
    public:
        Event() { }
        Event(function<void()> && f) : m_f(std::move(f)) { }
        void SetF(function<void()> && f) { m_f = std::move(f); }
    
    private:
        function<void()> m_f;
    };
    
    int main() {
    
        auto lambda = [](Thing &thing) { std::cout << thing.x << "\n"; };
    
        Thing thing;
        std::cout << "without unique_ptr: \n";
        Event ev(std::bind(lambda,thing));
        std::cout << "\n";
    
        std::cout << "with unique_ptr, no make_unique\n";
        unique_ptr<Event> ev_p(new Event(std::bind(lambda,thing)));
        std::cout << "\n";
    
        std::cout << "with make_unique: \n";
        auto ev_ptr = make_unique<Event>(std::bind(lambda,thing));
        std::cout << "\n";
    
        std::cout << "with SetF: \n";
        ev_ptr.reset(nullptr);
        ev_ptr = make_unique<Event>();
        ev_ptr->SetF(std::bind(lambda,thing));
        std::cout << "\n";
    
        return 0;
    }
    

    输出:
    g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
    or
    clang++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
    
    without unique_ptr:
    Copy constructed Thing!
    Move constructed Thing!
    
    with unique_ptr, no make_unique
    Copy constructed Thing!
    Move constructed Thing!
    
    with make_unique:
    Copy constructed Thing!
    Move constructed Thing!
    Move constructed Thing!
    
    with SetF:
    Copy constructed Thing!
    Move constructed Thing!
    

    PS:我用C++ 11和14标记了这个问题,因为当使用此处找到的常用make_unique函数(make_unique and perfect forwarding)将C++ 11标志传递给gcc时,会发生相同的问题

    最佳答案

    我认为使用make_unique时的其他移动是由于Event(std::bind(lambda,thing))中的移动省略。

    被称为Event的构造函数是Event(function<void()> && f),因此必须创建一个临时的function<void()>。使用std::bind表达式的返回值初始化此临时文件。

    用于执行这种从std::bind的返回类型到std::function<void()>的返回类型的转换的构造函数采用值作为参数:

    template<class F> function(F f); // ctor
    

    这意味着我们必须将std::bind的返回值移动到f的构造函数的此参数function<void()>。但是该举动有资格进行举动省略。

    当我们通过make_unique传递该临时变量时,它已绑定(bind)到引用,并且可能不再应用移动省略。因此,如果我们抑制移动省略:
    std::cout << "with unique_ptr, no make_unique\n";
    unique_ptr<Event> ev_p(new Event(suppress_elision(std::bind(lambda,thing))));
    std::cout << "\n";
    
    std::cout << "with make_unique: \n";
    auto ev_ptr = make_unique<Event>(suppress_elision(std::bind(lambda,thing)));
    std::cout << "\n";
    

    (我们可以使用std::move作为suppress_elision的实现。)

    我们可以观察到相同数量的移动:Live example

    解释整套操作:

    对于new Event(std::bind(lambda,thing)):

    操作|行为
    -------------------------------------------------- ---- + ----------
    `thing`变量->`bind`临时|拷贝
    `bind`临时->`function` ctor参数| Action (*)
    `function` ctor param->`function`对象(临时)| Action
    `function`临时->`Event` ctor ref参数| --
    `Event` ctor ref param->`function`数据成员| *可以*移动(+)

    (*)可被忽略
    (+),但不是,可能是因为内部函数对象在堆上,并且仅移动了指针。 Verify by replacing m_f(std::move(f)) with m_f()

    对于make_unique<Event>(std::bind(lambda,thing)):

    操作|行为
    -------------------------------------------------- ------ + ----------
    `thing`变量->`bind`临时|拷贝
    `bind`临时->`make_unique` ref参数| --
    `make_unique` ref param->`function` ctor param | Action
    `function` ctor param->`function`对象(临时)| Action
    `function`临时->`Event` ctor ref参数| --
    `Event` ctor ref param->`function`数据成员| *可以*移动(+)

    关于c++ - 为什么make_unique可以使用std::bind作为参数的构造函数有额外的 Action ?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27952709/

    10-17 01:40