【问题标题】:Initialization of member array with noncopyable non pod使用不可复制的非 pod 初始化成员数组
【发布时间】:2013-08-12 06:17:27
【问题描述】:

我认为最简单的提问方式是举个例子。假设我们有以下类型:

class Node
{
  // make noncopyable
  Node(const Node& ref) = delete;      
  Node& operator=(const Node& ref) = delete;

  // but moveable
  Node(Node&& ref) = default;
  Node& operator=(Node&& ref) = default;

  // we do not have a default construction
  Node() = delete;
  Node(unsigned i): _i(i) {}

  unsigned _i;
};

现在我想将其中一些节点存储在 std::array 中:

template<unsigned count>
class ParentNode
{
  std::array<Node,count> _children;
  ParentNode() 
     // i cannt do this, since i do not know how many nodes i need
     // : _children{{Node(1),Node(2),Node(3)}}  
     : _children()  // how do i do this?
  {}
};

正如评论中所述,问题是:我该怎么做?传递给孩子的无符号应该是存储孩子的数组的索引。但也非常感谢更通用的解决方案!

我发现自己的以下解决方案可能会导致更复杂类型的未定义行为。有关正确定义的解决方案,请参阅接受的答案。

template<unsigned count>
class ParentNode
{
public:
   // return by value as this will implicitly invoke the move operator/constructor
   std::array<Node,count> generateChildren(std::array<Node,count>& childs)
   {
      for (unsigned u = 0; u < count; u++)
         childs[u] = Node(u);  // use move semantics, (correct?)

      return std::move(childs); // not needed
      return childs;  // return by value is same as return std::move(childs)
   }

  std::array<Node,count> _children;

  ParentNode() 
     // i cannt do this, since i do not know how many nodes i need
     // : _children{{Node(1),Node(2),Node(3)}}  
     : _children(generateChildren(_children))  // works because of move semantics (?)
  {}
};

ParentNode<5> f; 

代码会编译。但我不确定它是否符合我的预期。也许对移动语义和右值引用有深入了解的人可以添加一些 cmets:-)

【问题讨论】:

  • 您的解决方案在技术上具有未定义的行为:因为当您将 _children 传递给 generateChildren 时它未初始化,所以循环正在对未初始化的对象调用移动赋值运算符。 (鉴于Node 有一个可以简单构造的成员,我承认这种未定义行为的最可能结果是完全按照您的意愿工作。)另外,我看到您的解决方案从 0 开始初始化节点,这并不'不同意代码中从 1 开始的示例。

标签: templates c++11 move-semantics rvalue-reference array-initialization


【解决方案1】:

您可以使用可变参数生成array,其中元素初始化为索引的任意函数。使用标准机制生成索引序列:

template <int... I> struct indices {};
template <int N, int... I> struct make_indices :
  make_indices<N-1,N-1,I...> {};
template <int... I> struct make_indices<0,I...> : indices<I...> {};

这很简单:

template <typename T, typename F, int... I>
inline std::array<T, sizeof...(I)> array_maker(F&& f, indices<I...>) {
  return std::array<T, sizeof...(I)>{ std::forward<F>(f)(I)... };
}

template <typename T, std::size_t N, typename F>
inline std::array<T, N> array_maker(F&& f) {
  return array_maker<T>(std::forward<F>(f), make_indices<N>());
}

这让我们可以做任何事情,而不是复制std::iota 的效果:

auto a = array_maker<int,10>([](int i){return i;});

用倒序排列的前 10 个自然数的平方组成一个数组:

const auto a = array_maker<std::string,10>([](int i){
  return std::to_string((10 - i) * (10 - i));
});

由于您的 Node 是可移动的,因此您可以将 ParentNode 构造函数定义为:

ParentNode() 
   : _children(array_maker<Node, count>([](unsigned i){return i+1;}))
{}

See it all put together live at Coliru.

【讨论】:

  • 非常感谢 :-) 如果 Node 只有以下构造函数:Node(unsigned,someothertype),这种方法是否也有效?也许您也可以评论我自己的解决方案?干杯
  • 是的 - 我已经用通用的弯曲重写了答案。任何可以作为数组索引函数计算的东西,都可以用来生成数组。我在示例中利用了您的Node 可以从unsigned 隐式转换的事实,但是没有理由初始化函数不能直接返回,例如Node(i, someotherthing).
  • 伙计,你是个天才 :-) 非常感谢这次编辑,我学到了很多!
  • 只是一个小更新,在c++14中有一个内置的index range
【解决方案2】:

真的,你无能为力。你想把一个没有默认构造函数的类型放入一个大小由模板参数确定的数组中,然后想用一些任意值初始化元素。

没有任何东西可以从一个函数返回,它可以放入一个花括号初始化列表并用于初始化一个包含多个元素的数组(或任何类型的聚合)。 {} 并不意味着“initializer_list”。它是一个花括号初始化列表,在某些情况下可以成为initializer_list,但它也可以成为构造函数调用的参数或在聚合初始化中使用的元素。

您最好的选择实际上是只使用 vector 并使用循环手动初始化它。

【讨论】:

  • “真的,你无能为力。”我认为这是:“我根本无法以描述的方式做到这一点”并接受您的回答。因为我真的想使用静态大小的数组,我将不得不更改节点接口:-(
猜你喜欢
  • 1970-01-01
  • 2020-05-15
  • 2011-08-04
  • 2016-03-03
  • 2019-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多