为什么make_unique使用可以将std :: bind作为参数的构造函数进行额外的移动?

时间:2015-01-14 21:22:44

标签: c++ c++11 c++14

我有一个简单的类,其构造函数如下:

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)));

以上述方式使用它会导致一个副本构造“&#39;”,然后在该副本上移动构造。

但是,请执行以下操作:

std::unique_ptr<Event> ev = make_unique<Event>(std::bind(some_func,thing));

两个移动结构的结果。我的问题是:

  • 什么时候移动构造函数确实在&#39;&#39;称为
  • 为什么用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函数将C ++ 11标志传递给gcc时会出现同样的问题(make_unique and perfect forwarding

1 个答案:

答案 0 :(得分:9)

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

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

用于执行此转换的构造函数从返回类型std::bindstd::function<void()>接受参数按值

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

这意味着我们必须将std::bind的返回值移动到f的构造函数的此参数function<void()>。但是这一举动符合 move elision 的条件。

当我们通过make_unique传递该临时值时,它已被绑定到引用并且移动省略可能不再适用。如果我们因此压制移动省略:

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))

operation                                             | behaviour 
------------------------------------------------------+----------
`thing` variable        ->  `bind` temporary          | copies
`bind` temporary        ->  `function` ctor param     | moves (*)
`function` ctor param   ->  `function` object (temp)  | moves
`function` temporary    ->  `Event` ctor ref param    | -
`Event` ctor ref param  ->  `function` data member    | *can* move (+)

(*)可以省略 (+)但不是,可能是因为内部函数对象在堆上,只有一个指针被移动。 Verify by replacing m_f(std::move(f)) with m_f()

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

operation                                               | behaviour 
--------------------------------------------------------+----------
`thing` variable         ->  `bind` temporary           | copies
`bind` temporary         ->  `make_unique` ref param    | -
`make_unique` ref param  ->  `function` ctor param      | moves
`function` ctor param    ->  `function` object (temp)   | moves
`function` temporary     ->  `Event` ctor ref param     | -
`Event` ctor ref param   ->  `function` data member     | *can* move (+)