我可以列表初始化仅移动类型的向量吗?
如果我通过 GCC 4.7 快照传递以下代码,它会尝试将 unique_ptr
复制到向量中。
#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 尝试从初始化列表复制指针是否正确?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
编辑:由于@Johannes似乎不想发布最佳解决方案作为答案,所以我就这样做。
std::make_move_iterator
返回的迭代器将在取消引用时移动指向的元素。原始答案:
我们将在这里使用一些辅助类型:
遗憾的是,这里直接的代码不起作用:
因为无论出于何种原因,标准都没有定义如下所示的转换复制构造函数:
initializer_list{...}
) 创建的 ;move_only>> 不会转换为向量
采用的initializer_list
。所以我们这里需要两步初始化:Edit: Since @Johannes doesn't seem to want to post the best solution as an answer, I'll just do it.
The iterators returned by
std::make_move_iterator
will move the pointed-to element when being dereferenced.Original answer:
We're gonna utilize a little helper type here:
Sadly, the straight-forward code here won't work:
Since the standard, for whatever reason, doesn't define a converting copy constructor like this:
The
initializer_list<rref_wrapper<move_only>>
created by the brace-init-list ({...}
) won't convert to theinitializer_list<move_only>
that thevector<move_only>
takes. So we need a two-step initialization here:18.9 中
的概要清楚地表明初始化器列表的元素始终通过 const 引用传递。不幸的是,在当前版本的语言中,似乎没有任何方法可以在初始值设定项列表元素中使用移动语义。具体来说,我们有:
The synopsis of
<initializer_list>
in 18.9 makes it reasonably clear that elements of an initializer list are always passed via const-reference. Unfortunately, there does not appear to be any way of using move-semantic in initializer list elements in the current revision of the language.Specifically, we have:
正如其他答案中提到的, std::initializer_list 的行为是按值保存对象并且不允许移出,因此这是不可能的。以下是一种可能的解决方法,即使用函数调用,其中初始值设定项作为可变参数给出:
不幸的是
multi_emplace(foos, {});
失败,因为它无法推导出{},因此对于默认构造的对象,您必须重复类名。 (或使用
vector::resize
)As mentioned in other answers, the behaviour of
std::initializer_list
is to hold objects by value and not allow moving out, so this is not possible. Here is one possible workaround, using a function call where the initializers are given as variadic arguments:Unfortunately
multi_emplace(foos, {});
fails as it cannot deduce the type for{}
, so for objects to be default-constructed you have to repeat the class name. (or usevector::resize
)C++20 更新:
将 Johannes Schaub 的
std::make_move_iterator()
技巧与 C++20 的std::to_array()
结合使用,您可以使用类似于make_tuple( )
等,这里称为make_vector()
:在 Godbolt。
对于旧版 C++ 的类似答案:
使用 Johannes Schaub 的
std::make_move_iterator()
技巧和std::experimental::make_array()
,您可以使用辅助函数:参见它位于 Coliru。
也许有人可以利用
std::make_array()
的诡计来允许make_vector()
直接做它的事情,但我没有看到如何(更准确地说,我尝试过我认为应该有效的,失败了,然后继续前进)。无论如何,编译器应该能够内联数组到向量转换,就像 Clang 在 上使用 O2 所做的那样GodBolt。Update for C++20:
Using Johannes Schaub's trick of
std::make_move_iterator()
with C++20'sstd::to_array()
, you can use a helper function like untomake_tuple()
etc., here calledmake_vector()
:See it live on Godbolt.
Similar answer for older C++:
Using Johannes Schaub's trick of
std::make_move_iterator()
withstd::experimental::make_array()
, you can use a helper function:See it live on Coliru.
Perhaps someone can leverage
std::make_array()
's trickery to allowmake_vector()
to do its thing directly, but I did not see how (more accurately, I tried what I thought should work, failed, and moved on). In any case, the compiler should be able to inline the array to vector transformation, as Clang does with O2 on GodBolt.为此,我创建了一个小型库。
在 gcc.godbolt.org 上运行
与
move_iterator
方法不同,这不一定会移动每个元素。nullptr
直接放入向量中,而不构造中间std::unique_ptr
。这使得它甚至可以与不可移动的类型一起工作:
I've made a small library for this purpose.
run on gcc.godbolt.org
Unlike the
move_iterator
approach, this doesn't necessarily move each element.nullptr
is emplaced directly into the vector, without constructing an intermediatestd::unique_ptr
.This allows it to work even with non-movable types:
这是我最喜欢的解决方案。
C++17版本
有点丑 C++11版本
This is the solution I like the most.
C++17 version
A bit uglier C++11 version
尝试为我们其他人提供一个简单中肯的答案。
你不能。它坏了。
幸运的是,数组初始值设定项没有被破坏。
如果您想使用该数组来初始化
std::vector>
,有无数种方法可以做到这一点,其中许多涉及巴洛克式的令人不快的模板元编程,所有这些都可以通过 for 循环来避免。幸运的是,在很多情况下,您确实更愿意使用 std::vector,使用数组而不是 std::vector 是可行的。
或者,考虑编写一个> 不会在合理的时间内或合理的努力下工作。您可以删除任何执行潜在移动的方法(移动和复制构造函数,
custom::static_vector
类,该类在初始值设定项列表中获取T*
,并在其析构函数中删除它们。也不高兴,但您需要接受这样一个事实:std::vectorT&operator[]()
&c)。或者,如果您必须(但您可能不会),请尝试并实现基本的移动语义。参见[1]对此的辩护,为纯粹主义神职人员提供。
[1] 编程语言应该提高生产力。
在这种情况下,模板元编程不会这样做。所有我
想要的是一种确保我不会泄漏分配的内存的方法
静态初始化到堆中,从而使其不可能
使用 valgrind 来验证我没有泄漏内存。
这是一个日常用例。这应该不难。让它变得过于复杂只会导致走捷径。
An attempt at a simple to-the-point answer for the rest of us.
You can't. It's broken.
Fortunately, array initializers aren't broken.
If you then want to use that array to initialize a
std::vector<std::unique_ptr<T>>
, there are endless ways to do so, many of which involve baroquely unpleasant template metaprogramming, all of which can be avoided with a for loop.Fortunately, using an array instead of a std::vector works in a lot of cases where you really would have preferred to use a std::vector.
Alternately, consider writing a
custom::static_vector<T>
class that takeT*
's in an initializer list, and deletes them in its's destructor. Also not happy, but you need to resign yourself to the fact thatstd::vector<std::unique_ptr<T>>
isn't going to work in reasonable time or with reasonable effort. You can just delete any methods that do a potential move (move and copy constructors,T&operator[]()
&c). Or get fancy and implement rudimentary move semantics if you must (but you probably don't).See [1] for a defense of this, provided for members of the Purist priesthood.
[1] Programming languages are supposed to increase productivity.
Template meta-programming isn't doing that in this case. All I
want is a way to ensure that I don't leak memory allocated in
static initialization into the heap, thereby making it impossible
to use valgrind to verify that I'm not leaking memory.
That's an everyday use-case. And it shouldn't be difficult. Making it remotely complicated only leads to shortcuts down the road.
正如已经指出的那样,不可能使用初始值设定项列表来初始化仅移动类型的向量。 @Johannes 最初提出的解决方案工作正常,但我有另一个想法......如果我们不创建临时数组,然后将元素从那里移动到向量中,而是使用放置
new
来,会怎么样?初始化这个数组已经代替向量的内存块了吗?这是我使用参数包初始化
unique_ptr
向量的函数:As it has been pointed out, it is not possible to initialize a vector of move-only type with an initializer list. The solution originally proposed by @Johannes works fine, but I have another idea... What if we don't create a temporary array and then move elements from there into the vector, but use placement
new
to initialize this array already in place of the vector's memory block?Here's my function to initialize a vector of
unique_ptr
's using an argument pack: