如何构造一个包含非默认构造对象的std::vector?

3

我在C++参考页面上看到了这样的代码:

#include <algorithm>
#include <list>
#include <vector>
#include <functional>

int main()
{
    std::list<int> l = {-4, -3, -2, -1, 0, 1, 2, 3, 4};
    std::vector<std::reference_wrapper<int>> v(l.begin(), l.end());
    return 0;
}

这是“示例”部分的代码片段。代码按预期编译和运行。但是,这是怎么可能的呢?std::reference_wrapper<int>不能被默认构造。你怎么能使一个 std::vector 包含这些东西?我一直将 std::vector 想象成动态数组。但你如何从 std::list 初始化一个刚从操作系统给出的一块内存块?这可能听起来很困惑,但由于某种原因,我无法完全理解上面的代码。那里发生了什么?

我一直认为它是用空字节初始化的,如果有构造函数,则调用它。 - Cole Tobin
似乎你不能向这种类型的向量中添加元素,但是当它被构建时,它会调用l中每个元素的非默认构造函数,例如std::reference<int>(-4)std::reference<int>(-3)等。 - Rob I
1
@Robl:是的,您可以使用任何不需要默认构造函数的方法对其进行添加。 - Benjamin Lindley
@BenjaminLindley emplace_back - balki
1
@balki: 对,就是那个。或者 emplaceinsertpush_back 或者 resize(带有两个参数的版本)。事实上,唯一需要默认构造函数的向量方法是单参数版本的 resize,以及只接受 int 参数进行初始大小设置(或 int 和分配器)的构造函数。 - Benjamin Lindley
3个回答

6

这段代码之所以能够工作,是因为没有默认初始化——列表中的元素用于复制初始化向量。

例如,以下代码将无法正常工作:

std::vector<std::reference_wrapper<int>> v(42);

哦,我明白了。如果我编写一个模板类,一些方法不能适用于所有模板类型参数,这样可以吗?这被认为是良好的实践还是只是标准库的特权? - Martin Drozdik
3
有一些限制,但是在方法没有实例化的情况下就可以。然而有时候方法可能会在您没有请求的情况下被实例化,例如virtual方法总是被实例化的。 - Matthieu M.

2
std::vector<>的构造函数首先使用其allocator获得适当大小的原始内存块。然后再构造对象。在这里特定的构造函数的情况下。
template<typename It>
std::vector::vector(It begin, It end);

它根据迭代器的valuetype构建元素,因此每个 reference_wrapper<int> 都是从一个 int 构建(就地构造)。


1
所有向量中的对象都使用一个接受int参数的构造函数构建。因此,语义上不需要默认构造函数。
就实现而言,可以通过使用malloc分配内存(因此不会调用构造函数),然后在向量添加元素时使用放置new来实现此行为。

所以这就是其中的奥妙。我一直以为std::vector在内部使用new。这很有道理。 - Martin Drozdik
@MartinDrozdik 它可以在内部使用 new -- 但是使用 new unsigned char[size*sizeof(T)] 而不是 new T[size] - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接