3

想象一下,我想在std::vector没有移动或复制构造函数的情况下构造固定大小的对象,例如std::atomic<int>. 在这种情况下,底层std::atomic类有一个 1-arg 构造函数,它接受一个int,以及一个默认构造函数(将值初始化为 0)。

使用initializer_listlike 语法std::vector<std::atomic<int>> v{1,2,3}不起作用,因为T作为创建的一部分,参数首先转换为向量的元素类型,initializer_list因此将调用复制或移动构造函数。

在特定情况下,std::atomic<int>我可以默认构造向量,然后在以下情况下对元素进行变异:

std::vector<std::atomic<int>> v(3);
v[0] = 1;
v[1] = 2;
v[2] = 3;

然而,除了丑陋和低效之外,它不是一个通用的解决方案,因为许多对象可能无法提供与通过调用适当的构造函数可以获得的构造后突变等效。

有什么方法可以在向量构造中获得我想要的“类似位置”的行为?

4

1 回答 1

2

一个通用的解决方案是让您的向量采用自定义分配器,其construct方法执行适当的初始化。在下面的代码中,v使用MyAllocator<NonMovable>分配器而不是std::allocator<NonMovable>. 当construct不带参数调用该方法时,它实际上是使用适当的参数调用构造函数。这样,默认构造函数就可以正确初始化元素。

(简单地说,我在这个例子中设置了静态,但它也可以是一个在构造next_value时初始化的非静态成员变量。)MyAllocator

#include <stdio.h>
#include <memory>
#include <new>
#include <vector>

struct NonMovable {
    NonMovable(int x) : x(x) {}
    const int x;
};

template <class T>
struct MyAllocator {
    typedef T value_type;
    static int next_value;
    T* allocate(size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    void deallocate(T* p, size_t n) {
        ::operator delete(p);
    }
    template <class U>
    void construct(U* p) {
        new (p) U(++next_value);
    }
};

template <class T> int MyAllocator<T>::next_value = 0;

int main() {
    std::vector<NonMovable, MyAllocator<NonMovable>> v(10);
    for (int i = 0; i < 10; i++) {
        printf("%d\n", v[i].x);
    }
}

http://coliru.stacked-crooked.com/a/1a89fddd325514bf

当您不允许触摸NonMovable类并且其构造函数可能需要多个参数时,这是唯一可能的解决方案。在您只需要向每个构造函数传递一个参数的情况下,有一个更简单的解决方案,它使用 的范围构造函数std::vector,如下所示:

std::vector<int> ints(10);
std::iota(ints.begin(), ints.end(), 1);
std::vector<NonMovable> v(ints.begin(), ints.end());

(虽然如果你负担不起额外的内存,那么你将不得不编写一个自定义迭代器,这将是更多的代码。)

于 2017-10-13T22:07:25.930 回答