具有不可复制、不可移动元素类型的C++容器

8
我需要一个元素容器,这些元素既不可复制也不可移动。这些元素不能被默认构造,但它们的构造函数会接收相同的参数。
该容器在其生命周期内大小不会改变。它应该像内置数组一样简单,但是它的大小在运行时调用构造函数时确定。
有没有一种简单的方法可以实现它,而不需要使用std::vector>所产生的内存分配和间接开销?

你尝试过使用emplace函数吗?不过我不确定它们在非可移动情况下的工作效果如何。 - Hayt
你需要一个基于一次内存分配和使用placement-new的元素放置的解决方案。 - LogicStuff
5
std::vector<T> 无法编译非可复制和非可移动的 T 类型。使用 emplace() 插入元素需要向量增长(可能)。增长操作需要元素可以被复制或移动。 - Ralph Tandetzky
我相信有一个关于这个的高票问题,但我现在找不到它……这是向量的一个问题,即使内部结构支持此用法,它也没有正确的构造函数和成员函数。也许有一个类似于向量的容器开放了一个领域,它允许在构造时设置容量,并且具有非重新分配的emplace/resize(如果没有可用容量则抛出异常)。 - M.M
你是否有这样的要求,即可以使用&v[0]来访问存储,就像C风格的数组一样? - M.M
显示剩余3条评论
4个回答

3

这是一个简单的解决方案,但是假设每个元素都使用相同的参数构建。它使用就地new在原地构建元素(也可以参考这个SO问题):

#include <cstdlib>
#include <utility>
#include <new>

// sample structure, non-copyable, non-moveable, non-default-constructible
struct Foo
{
  Foo() = delete;
  Foo(const Foo&) = delete;
  Foo& operator = (const Foo&) = delete;
  Foo(Foo&&) = delete;
  Foo& operator = (Foo&&) = delete;

  Foo(int a, char b, double c) : m_a(a), m_b(b), m_c(c) { }

  int m_a;
  char m_b;
  double m_c;
};

template <typename T>
struct MyArray
{
  // Array ctor constructs all elements in-place using the
  // provided parameters
  template <typename... Args>
  MyArray(std::size_t sz, Args&&... args)
    : m_sz(sz),
      m_data(static_cast<T*>(malloc(sz * sizeof(T))))
  {
    for (std::size_t i=0; i<m_sz; ++i)
    {
      new (&m_data[i]) T(std::forward<Args>(args)...);
    }
  }

  ~MyArray()
  {
    for (std::size_t i=0; i<m_sz; ++i)
    {
      m_data[i].~T();
    }
    free(m_data);
  }

  std::size_t m_sz;
  T *m_data;
};

int main()
{
  Foo foo(1, '2', 3.0);
  std::size_t s = 5;
  MyArray<Foo> foo_arr(s, 1, '2', 3.0);
}

请注意以下几点:
  • 如果在MyArray的构造函数中抛出异常,这个基本实现会导致内存泄漏。
  • 你可能希望有一个遍历器实现、begin()/end()操作等,以获得更多便利,并获得标准容器提供的相同行为。
  • 仅为说明,我也没有关注适当的封装。你应该将m_szm_data设置为私有成员。

1
只是补充一下:这种技术被称为放置new。当我按下刷新并看到它已经发布时,我正在研究的正是这个完全相同的技术。 - KitsuneYMG

0
由于您要管理的元素既不可移动也不可复制,因此容器只能包含指向元素的指针。除非了解更多要求,否则很难猜测原始指针或std::unique_ptr哪个更合适。
对于固定大小的容器,可以使用std::array。不幸的是,大小必须是编译时表达式。
另一种选择是使用原始数组,并使用放置new在原地构建元素。您可以尝试使用向量而不是原始数组,因为向量像普通数组一样使用连续内存,但我无法想象如何将其用于存储不可复制,不可移动和不可默认构造的对象。
顺便说一下,在C ++中构建大小未知且不可默认构造的对象数组并不那么简单。我能找到的唯一方法是构建一个正确大小的char数组,声明指向类的指针指向该数组的开头,然后使用放置new构建元素:
char buf = new char[n * sizeof(X)];
X* x = reinterpret_cast<X*>(buf);
for(int i=-1; i<n i++) {
    new (x + i) X(i);  // or whatever appropriate ctor...
}

0

我也在尝试做同样的事情,而且我使用了一个简单的解决方法。 这个例子有什么问题吗?

class test{
  const int a;
public:
  test(int i): a(i) {} //no default constructor
};

//trick for static initializer
template <typename T> class defer_params: public T{
public:
  defer_params(): T(1) {} //fixed params
};

//static array
//test list1[5]; //this doesn't work
defer_params<test> list2[5];

谨此致意, Gabriel


-2

有没有一种简单的方法来实现它,而不需要使用std::vector>所产生的内存分配和间接性开销?

开销是如此微小,你为什么要在意呢?这几乎肯定是过早优化,你只会给自己带来维护上的麻烦。


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