2017-07-17 65 views
8

我有一个复制/移动探测类:为什么std :: vector在初始化时强制复制?

#include <iostream> 

struct A 
{ 
    A() 
    { 
     std::cout << "Creating A" << std::endl; 
    } 

    ~A() noexcept 
    { 
     std::cout << "Deleting A" << std::endl; 
    } 

    A(const A &) 
    { 
     std::cout << "Copying A" << std::endl; 
    } 

    A(A &&) noexcept 
    { 
     std::cout << "Moving A" << std::endl; 
    } 

    A &operator=(const A &) 
    { 
     std::cout << "Copy-assigning A" << std::endl; 
     return *this; 
    } 

    A &operator=(A &&) noexcept 
    { 
     std::cout << "Move-assigning A" << std::endl; 
     return *this; 
    } 
}; 

而且我发现,运行:

#include <vector> 

int main(int, char **) 
{ 
    std::vector<A> v { A() }; 
} 

产生以下输出:

Creating A 
Copying A 
Deleting A 
Deleting A 

为什么不会初始化只是移动对象?我知道std::vector可能会创建undesired copies on resize,但正如你所看到的,添加noexcept在这里没有帮助(此外,我不认为调整大小导致副本适用于初始化的原因)。

如果我不是做到以下几点:

std::vector<A> v; 
v.push_back(A()); 

我没有得到副本。

经GCC 5.4和Clang 3.8测试。

+9

这是因为正在使用'std :: initializer_list'构造函数。这种类型的语义是不幸的。 – StoryTeller

+3

本问答解释了这种情况,并提供了一些解决方法:https://stackoverflow.com/questions/8468774/can-i-list-initialize-a-vector-of-move-only-type –

+0

@StoryTeller @MM I看,所以问题不是'std :: vector',而是'std :: initializer_list',显然不能转发右值引用?如果有人可以请发表正确的答案,我会接受。 – jdehesa

回答

9

这不是std::vector,而是std::initializer_list

std::initializer_list由一个const元素数组支持。它不允许非const访问其数据。

这阻止从其数据移动。

但是,这是C++,这样我们就可以解决这个问题:

template<class T, class A=std::allocator<T>, class...Args> 
std::vector<T,A> make_vector(Args&&...args) { 
    std::array<T, sizeof...(Args)> tmp = {{std::forward<Args>(args)...}}; 
    std::vector<T,A> v{ std::make_move_iterator(tmp.begin()), std::make_move_iterator(tmp.end()) }; 
    return v; 
} 

现在我们得到:

auto v = make_vector<A>(A()); 

给你每件加1招:

Creating A 
Moving A 
Moving A 
Deleting A 
Deleting A 
Deleting A 

我们可以通过仔细的预留和放回来消除额外的实例:

template<class T, class A=std::allocator<T>, class...Args> 
std::vector<T,A> make_vector(Args&&...args) { 
    std::vector<T,A> v; 
    v.reserve(sizeof...(args)); 
    using discard=int[]; 
    (void)discard{0,(void(
    v.emplace_back(std::forward<Args>(args)) 
),0)...}; 
    return v; 
} 

Live example of both - 只需简单更换v2::v1::在行动中看到的第一个。

输出:

Creating A 
Moving A 
Deleting A 
Deleting A 

有可能是一个有点向量的开销在这里,因为它可能是很难的编译器,以证明emplace_back不会导致重新分配(即使我们可以证明这一点),所以多余的检查将很可能被编译。 (在我看来,如果没有足够的容量,我们需要一个emplace_back_unsafe即UB)。

额外损失的A s可能是值得的。

另一种选择:

template<std::size_t N, class T, class A=std::allocator<T>, class...Args> 
std::vector<T,A> make_vector(std::array<T, N> elements) { 
    std::vector<T,A> v{ std::make_move_iterator(elements.begin()), std::make_move_iterator(elements.end()) }; 
    return v; 
} 

这是用来像

auto v = make_vector<1,A>({{ A() }}); 

,你必须手动指定有多少元素。它与上面的版本2一样高效。

相关问题