【问题标题】:Emplacement-like construction for std::vectorstd::vector 的类似位置的构造
【发布时间】:2017-10-13 19:06:10
【问题描述】:

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

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

std::atomic<int> 的特殊情况下,我可以默认构造向量,然后在之后改变元素:

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

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

有没有什么方法可以在向量构造中获得我想要的“emplace-like”行为?

【问题讨论】:

  • 说真的,我只会使用std::deque。但如果你不能,唯一的方法就是通过自定义分配器来做你想做的事。
  • @Brian - std::deque 允许这种构造成语吗?
  • 使用std::deque,您必须一个一个地放置元素,但它会起作用,因为在开头或结尾添加元素不会移动任何其他元素。
  • @Brian - 对,至少在这方面std::vector 更好,不幸的是,有时我需要vector 提供的连续存储保证。我也对自定义分配器方法感兴趣:自定义分配器如何绕过 vector 构造函数接口对我来说并不明显。

标签: c++ vector emplace


【解决方案1】:

一般的解决方案是让您的向量采用自定义分配器,其construct 方法执行适当的初始化。在下面的代码中,v 使用MyAllocator&lt;NonMovable&gt; 分配器而不是std::allocator&lt;NonMovable&gt;。当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());

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

【讨论】:

    猜你喜欢
    • 2014-03-23
    • 2012-07-10
    • 1970-01-01
    • 2020-10-05
    • 2023-03-18
    • 2019-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多