我可以列出初始化一个仅移动类型的向量吗?
Can I list-initialize a vector of move-only type?
如果我通过GCC 4.7快照传递以下代码,它会尝试将unique_ptr
s复制到向量中。
#include <vector>
#include <memory>
int main() {
using move_only = std::unique_ptr<int>;
std::vector<move_only> v { move_only(), move_only(), move_only() };
}
显然不能工作,因为std::unique_ptr
是不可复制的:
std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int;_Dp = std::default_delete;std:: unique_ptr<_Tp _Dp> = std:: unique_ptr]
GCC在尝试从初始化列表中复制指针是正确的吗?
编辑:既然@Johannes似乎不想发布最佳解决方案作为答案,我就这么做了。
#include <iterator>
#include <vector>
#include <memory>
int main(){
using move_only = std::unique_ptr<int>;
move_only init[] = { move_only(), move_only(), move_only() };
std::vector<move_only> v{std::make_move_iterator(std::begin(init)),
std::make_move_iterator(std::end(init))};
}
std::make_move_iterator
返回的迭代器将在解引用时移动指向元素。
原始答:我们将在这里使用一个小helper类型:
#include <utility>
#include <type_traits>
template<class T>
struct rref_wrapper
{ // CAUTION - very volatile, use with care
explicit rref_wrapper(T&& v)
: _val(std::move(v)) {}
explicit operator T() const{
return T{ std::move(_val) };
}
private:
T&& _val;
};
// only usable on temporaries
template<class T>
typename std::enable_if<
!std::is_lvalue_reference<T>::value,
rref_wrapper<T>
>::type rref(T&& v){
return rref_wrapper<T>(std::move(v));
}
// lvalue reference can go away
template<class T>
void rref(T&) = delete;
遗憾的是,这里的直接代码不起作用:
std::vector<move_only> v{ rref(move_only()), rref(move_only()), rref(move_only()) };
由于某种原因,标准没有定义这样的转换复制构造函数:
// in class initializer_list
template<class U>
initializer_list(initializer_list<U> const& other);
由括号初始化列表({...}
)创建的initializer_list<rref_wrapper<move_only>>
不会转换为vector<move_only>
所使用的initializer_list<move_only>
。这里我们需要两步初始化:
std::initializer_list<rref_wrapper<move_only>> il{ rref(move_only()),
rref(move_only()),
rref(move_only()) };
std::vector<move_only> v(il.begin(), il.end());
18.9中<initializer_list>
的概要相当清楚地表明,初始化列表的元素总是通过const-reference传递。不幸的是,在当前版本的语言中,似乎没有任何方法可以在初始化列表元素中使用move-semantic。
具体来说,我们有:
typedef const E& reference;
typedef const E& const_reference;
typedef const E* iterator;
typedef const E* const_iterator;
const E* begin() const noexcept; // first element
const E* end() const noexcept; // one past the last element
如其他答案所述,std::initializer_list
的行为是按值保存对象,不允许移出,所以这是不可能的。这里有一个可能的解决方案,使用函数调用,其中初始化式作为可变参数给出:
#include <vector>
#include <memory>
struct Foo
{
std::unique_ptr<int> u;
int x;
Foo(int x = 0): x(x) {}
};
template<typename V> // recursion-ender
void multi_emplace(std::vector<V> &vec) {}
template<typename V, typename T1, typename... Types>
void multi_emplace(std::vector<V> &vec, T1&& t1, Types&&... args)
{
vec.emplace_back( std::move(t1) );
multi_emplace(vec, args...);
}
int main()
{
std::vector<Foo> foos;
multi_emplace(foos, 1, 2, 3, 4, 5);
multi_emplace(foos, Foo{}, Foo{});
}
不幸的是,multi_emplace(foos, {});
失败了,因为它不能推断出{}
的类型,所以对于默认构造的对象,您必须重复类名。(或使用vector::resize
)
更新c++ 20 :使用Johannes Schaub的std::make_move_iterator()
技巧和c++ 20的std::to_array()
,您可以使用类似于make_tuple()
等的辅助函数,这里称为make_vector()
:
#include <array>
#include <memory>
#include <vector>
struct X {};
template<class T, std::size_t N>
auto make_vector( std::array<T,N>&& a )
-> std::vector<T>
{
return { std::make_move_iterator(std::begin(a)),
std::make_move_iterator(std::end(a)) };
}
template<class... T>
auto make_vector( T&& ... t )
{
return make_vector( std::to_array({ std::forward<T>(t)... }) );
}
int main()
{
using UX = std::unique_ptr<X>;
const auto a = std::to_array({ UX{}, UX{}, UX{} }); // Ok
const auto v0 = make_vector( UX{}, UX{}, UX{} ); // Ok
//const auto v2 = std::vector< UX >{ UX{}, UX{}, UX{} }; // !! Error !!
}
观看现场直播Godbolt。
旧c++的类似答案:
使用Johannes Schaub的std::make_move_iterator()
与std::experimental::make_array()
的技巧,您可以使用辅助函数:
#include <memory>
#include <type_traits>
#include <vector>
#include <experimental/array>
struct X {};
template<class T, std::size_t N>
auto make_vector( std::array<T,N>&& a )
-> std::vector<T>
{
return { std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)) };
}
template<class... T>
auto make_vector( T&& ... t )
-> std::vector<typename std::common_type<T...>::type>
{
return make_vector( std::experimental::make_array( std::forward<T>(t)... ) );
}
int main()
{
using UX = std::unique_ptr<X>;
const auto a = std::experimental::make_array( UX{}, UX{}, UX{} ); // Ok
const auto v0 = make_vector( UX{}, UX{}, UX{} ); // Ok
//const auto v1 = std::vector< UX >{ UX{}, UX{}, UX{} }; // !! Error !!
}
在Coliru上观看。
也许有人可以利用std::make_array()
的诡计来允许make_vector()
直接做它的事情,但我没有看到如何(更准确地说,我尝试了我认为应该工作的,失败了,然后继续前进)。在任何情况下,编译器都应该能够将数组内联到向量转换,就像Clang在GodBolt上对O2所做的那样。
这是我最喜欢的解决方案
c++ 17版本#include <vector>
#include <memory>
template <typename T, typename ...Args>
std::vector<T> BuildVectorFromMoveOnlyObjects(Args&&... args) {
std::vector<T> container;
container.reserve(sizeof...(Args));
((container.emplace_back(std::forward<Args>(args))), ...);
return container;
}
int main() {
auto vec = BuildVectorFromMoveOnlyObjects<std::unique_ptr<int>>(
std::make_unique<int>(10),
std::make_unique<int>(50));
}
更丑的c++ 11版本
template <typename T, typename ...Args>
std::vector<T> BuildVectorFromMoveOnlyObjects(Args&&... args) {
std::vector<T> container;
using expander = int[];
(void)expander{0, (void(container.emplace_back(std::forward<Args>(args))), 0)... };
return container;
}
我为此制作了一个小库。
run on gcc.godbolt.org
#include <better_braces.hpp>
#include <iostream>
#include <memory>
#include <vector>
int main()
{
std::vector<std::unique_ptr<int>> foo = init{nullptr, std::make_unique<int>(42)};
std::cout << foo.at(0) << 'n'; // 0
std::cout << foo.at(1) << " -> " << *foo.at(1) << 'n'; // 0x602000000010 -> 42
}
与move_iterator
方法不同,这并不需要移动每个元素。直接将nullptr
放置到vector中,不构造中间的std::unique_ptr
。
这允许它在非可移动类型下工作:
std::vector<std::atomic_int> bar = init{1, 2, 3};
试图给我们其余的人一个简单扼要的答案。
你不能。它坏了。
幸运的是,数组初始化式没有被破坏。
static std::unique_ptr<SerializerBase> X::x_serializers[] = {
std::unique_ptr<SerializerBase>{
new Serializer<X,int>("m_int",&X::m_int)
},
std::unique_ptr<SerializerBase>{
new Serializer<X,double>("m_double",&X::m_double)
},
nullptr, // lol. template solutions from hell possible here too.
};
如果你想用这个数组来初始化一个std::vector<std::unique_ptr<T>>
,有无数种方法可以做到这一点,其中许多都涉及到令人不快的模板元编程,所有这些都可以通过for循环来避免。
幸运的是,在很多情况下,使用数组而不是std::vector可以工作,而这些情况实际上是您更愿意使用std::vector的。
或者,考虑编写一个custom::static_vector<T>
类,它在初始化列表中接受T*
,并在析构函数中删除它们。也不高兴,但你需要接受这样一个事实,即std::vector<std::unique_ptr<T>>
不会在合理的时间或合理的努力下工作。您可以删除任何执行潜在移动的方法(移动和复制构造函数,T&operator[]()
&c)。如果你需要的话,也可以更花哨一点,实现基本的move语义(但你可能不会这么做)。
参见[1]对此的辩护,这是为纯粹派祭司提供的。
[1]编程语言应该提高生产力。在这种情况下,模板元编程没有做到这一点。我所有的我想要的是一种方法,以确保我不泄漏分配的内存静态初始化到堆中,因此不可能使用valgrind来验证我没有泄漏内存。
这是一个日常用例。这应该不难。把它弄得过于复杂只会让你走捷径。
前面已经指出,不能用初始化列表初始化只移动类型的向量。@Johannes最初提出的解决方案很好,但我有另一个想法…如果我们不创建一个临时数组,然后将元素从那里移动到向量中,而是使用放置new
来初始化这个数组,该数组已经取代了向量的内存块,该怎么办?
下面是我使用参数包初始化一个unique_ptr
的向量的函数:
#include <iostream>
#include <vector>
#include <make_unique.h> /// @see http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding
template <typename T, typename... Items>
inline std::vector<std::unique_ptr<T>> make_vector_of_unique(Items&&... items) {
typedef std::unique_ptr<T> value_type;
// Allocate memory for all items
std::vector<value_type> result(sizeof...(Items));
// Initialize the array in place of allocated memory
new (result.data()) value_type[sizeof...(Items)] {
make_unique<typename std::remove_reference<Items>::type>(std::forward<Items>(items))...
};
return result;
}
int main(int, char**)
{
auto testVector = make_vector_of_unique<int>(1,2,3);
for (auto const &item : testVector) {
std::cout << *item << std::endl;
}
}
- 为什么协程的返回类型必须是可移动构造的?
- 如何获取类型是否真正可移动可构造
- 在从仅移动类型派生的类中定义析构函数在使用 std::vector emplace_back或push_back创建时会
- 对于可移动类型,按值传递比重载函数更好吗?
- 强制复制(然后销毁)仅移动类型
- 是否有可能具有放入容器的移动操作的类型?
- 错误:移动文件时'QString'为非标量类型"std::__cxx11::字符串"
- boost multi_index - 如果元素类型仅支持移动语义,如何遍历它?
- static_cast为仅移动类型的叮当与 GCC
- 从 std::set 中提取仅移动类型
- 具有抑制移动构造/赋值的类型如何仍被视为可移动类型?
- 包含仅移动类型的类的构造函数应通过引用还是通过右值引用接收仅移动类型?
- std::仅移动类型列表:不能在 VC++ 中放入 std::vector
- 如何将双精度类型的鼠标移动 x 和 y 从主机传递到 Qemu 中的虚拟机
- C :通过STD :: simolor_ptr(仅移动类型)rvalue作为参数时复制ELISION
- 为什么 std::vector 允许对其包含的类型使用可抛出的移动构造函数?
- STD ::变体与STD ::任何当类型移动时可构造时
- 使用模板将语义从一种类型移动到另一种类型
- 关于类型移动语义的注意事项
- 扩展std::vector以从其他vector类型移动元素