固定动态大小的容器

2022-01-24 00:00:00 sequence containers c++ c++11 std

是否有用于固定长度序列的标准容器,其中该长度在运行时确定.最好,我想将一个参数传递给每个序列元素的构造函数,并使用该参数来初始化一个 const 成员(或引用).我还想在 O(1) 中的给定索引处获取序列元素.在我看来,我的所有要求都无法同时满足.

Is there a standard container for a sequence of fixed length, where that length is determined at runtime. Preferrably, I'd like to pass an argument to the constructor of each sequence element, and use that argument to initialize a const member (or a reference). I'd also like to obtain the sequence element at a given index in O(1). It seems to me that all of my requirements cannot be met at the same time.

  • 我知道 std::array 有固定长度,但必须在编译时知道该长度.
  • std::vector 具有动态大小,并允许使用 emplace 传递构造函数参数.虽然您可以 reserve 内存以避免实际重新分配,类型仍然必须是 movable 理论上允许这样的重新分配,例如阻止 const 成员.
  • 然后是 std::liststd::forward_list,它们没有需要可移动类型,但仍可调整大小,并且在随机访问模式下表现不佳.我也觉得这样的列表可能会有相当大的开销,因为每个列表节点可能会被单独分配.
  • 奇怪的是,std::valarray到目前为止是我最好的选择,因为它有固定的长度并且不会自动调整大小.虽然有 resize 方法,但您的除非您实际调用该方法,否则类型不必是可移动的.这里的主要缺陷是缺少自定义构造函数参数,因此无法使用这种方法初始化 const 成员.
  • I know std::array has fixed length, but that length has to be known at compile-time.
  • std::vector has dynamic size, and allows passing contructor arguments using emplace. Although you can reserve memory to avoid actual reallocations, the type still has to be movable to theoretically allow such reallocations, which e.g. prevents const members.
  • Then there is std::list and std::forward_list, which don't require a movable type, but which are still resizable and will perform rather poorly under random-access patterns. I also feel that there might be considerable overhead associated with such lists, since each list node will likely be allocated separately.
  • Strangely enough, std::valarray is my best bet so far, since it has a fixed length and won't resize automatically. Although there is a resize method, your type won't have to be movable unless you actually call that method. The main deficit here is the lack for custom constructor arguments, so initializing const members isn't possible with this approach.

我错过了一些替代方案吗?有什么方法可以调整其中一个标准容器以满足我的所有要求吗?

Is there some alternative I missed? Is there some way to adjust one of the standard containers in such a way that it satisfies all of my requirements?

为了让您更准确地了解我正在尝试做什么,请参阅以下示例:

To give you a more precise idea of what I'm trying to do, see this example:

class A {
  void foo(unsigned n);
};

class B {
private:
  A* const a;
  const unsigned i;
public:
  B(A* aa) : a(aa), i(0) { }
  B(A* aa, unsigned ii) : a(aa), i(ii) { }
  B(const std::pair<A*, unsigned>& args) : B(args.first, args.second) { }
  B(const B&) = delete;
  B(B&&) = delete;
  B& operator=(const B&) = delete;
  B& operator=(B&&) = delete;
};

void A::foo(unsigned n) {
  // Solution using forward_list should be guaranteed to work
  std::forward_list<B> bs_list;
  for (unsigned i = n; i != 0; --i)
    bs_list.emplace_front(std::make_pair(this, i - 1));

  // Solution by Arne Mertz with single ctor argumen
  const std::vector<A*> ctor_args1(n, this);
  const std::vector<B> bs_vector(ctor_args1.begin(), ctor_args1.end());

  // Solution by Arne Mertz using intermediate creator objects
  std::vector<std::pair<A*, unsigned>> ctor_args2;
  ctor_args2.reserve(n);
  for (unsigned i = 0; i != n; ++i)
    ctor_args2.push_back(std::make_pair(this, i));
  const std::vector<B> bs_vector2(ctor_args2.begin(), ctor_args2.end());
}

推荐答案

理论上 vector 有你需要的属性.如您所述,如果元素不可复制和/或不可分配,则不支持可能对包含类型进行分配的操作,尤其是任何序列修改(empace_back、push_back、插入等).因此,要创建不可复制元素的向量,您必须在向量构造期间构造每个元素.

Theoretically vector has the properties you need. As you noted, actions that possibly do assignments to the contained type, including especially any sequence modifications (empace_back, push_back, insert etc.) are not supported if the elements are noncopyable and/or nonassignable. So to create a vector of noncopyable elements, you'd have to construct each element during vector construction.

正如 Steve Jessop 在他的回答中指出的那样,如果您首先定义向量 const,您甚至无法调用此类修改操作 - 当然元素也保持不变.

As Steve Jessop points out in his answer, if you define the vector const in the first place you won't even be able to call such modifying actions - and of course the elements remain unchanged as well.

如果我理解正确,您只有一个构造函数参数序列,而不是真正的对象序列.如果它只有一个参数并且包含的??类型有相应的构造函数,事情应该很容易:

If I understand correctly, you have only a sequence of constructor arguments, not the real object sequence. If it's only one argument and the contained type has a corresponding constructor, things shoule be easy:

struct C
{
  const int i_;  
  C(int i) : i_(i) {}
};

int main()
{
  const std::vector<C> theVector { 1, 2, 3, 42 };
}

如果构造函数是显式的,则必须先创建一个列表或显式构造initializer-list中的对象:

If the constructor is explicit, you have to make a list first or explicitly construct the objects in the initializer-list:

int main()
{
  auto list = { 1, 2, 3, 4 };
  const std::vector<C> theVector (std::begin(list), std::end(list));
  const std::vector<C> anotherVector { C(1), C(44) };
}

如果每个构造对象不止一个参数,请考虑使用中间创建者对象:

If it's more than just one argument per constructed object, consider a intermediate creator object:

struct C
{
  const int i_;  
  C(int i, int y) : i_(i+y) {}
};

struct CCreator
{ 
  int i; int y; 
  explicit operator C() { return C(i,y); }
};

int main()
{
  const std::vector<CCreator> ctorArgs = { {1,2}, {3,42} };
  const std::vector<C> theVector { begin(ctorArgs), end(ctorArgs) };
}

相关文章