在成员初始化列表中填充std::array

11
以下代码可以使用,但我希望避免警告:

警告: 'fitness::vect_' 应该在成员初始化列表中初始化 [-Weffc++]

当编译时使用g++ -Weffc++开关。
#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v)
  {
    static_assert(N, "fitness zero length");

    vect_.fill(v);
  }

private:
  std::array<T, N> vect_;
};

int main()
{
  fitness<double, 4> f(-1000.0);

  return 0;
}

我应该忽略这个警告吗?有没有一种方法可以在构造函数初始化列表中填充 vect_ (而不改变其类型)?


2
填充它并不容易,但你可以将其初始化为 vect_()vect_{} - juanchopanza
5个回答

4

我相信你可以忽略这个警告。

如果在构造函数中为数组进行一个空的初始化,它就能正常工作:

#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v):
  vect_{}
  {
    static_assert(N, "fitness zero length");

    vect_.fill(v);
  }

private:
  std::array<T, N> vect_;
};

int main()
{
  fitness<double, 4> f(-1000.0);

  return 0;
}

2
尝试使用

explicit fitness(T v) : vect_{}
{
//...
}

2

在这种情况下,默认构造函数(即值初始化程序)应该可以正常工作。由于std::array是一种聚合类型,其元素将分别进行值初始化,对于像double这样的数字类型来说,这意味着零初始化,然后您可以使用fill

explicit fitness(T v) : vect_() // or vect_{}
{
    vect_.fill(v);
}

如果您不想进行基本上双倍的初始化,那么最好使用std::vector及其填充构造函数。然后您的类将变为:
template<class T>
class fitness
{
public:
  explicit fitness(T v, unsigned n) : vect_(n, v)

private:
  std::vector<T> vect_;
};

int main()
{
   fitness<double> f(-1000.0, 4);

   return 0;
}

当然,您仍然可以将N作为模板参数保留,但由于长度不需要在编译时知道,因此没有必要这样做。(另一方面,如果您坚持使用std::array,则可能可以将构造函数设置为constexpr,尽管这可能需要一些模板操作或一个返回初始化列表的辅助constexpr函数才能正常工作,我还没有在C++11概念方面进行足够的实验。)


最初我使用了std::vector,但是std::array证明了在我的框架中具有更好的性能(fitness类对我的框架至关重要)。我将检查编译器是否能够避免双重初始化。 - manlio
由于std::array是一个聚合体,因此默认构造不会将内存清零,而是将其保留为未初始化状态(因此不会进行双重初始化)。 - manlio
如果您使用向量(vector)正确的话,它比数组慢得明显的情况是很少见的。 - Lightness Races in Orbit
根据http://en.cppreference.com/w/cpp/language/value_initialization和http://en.cppreference.com/w/cpp/language/aggregate_initialization,使用构造函数中的空表达式列表确实会将C++11中聚合体的(适用)成员清零;如果您使用`vect_()`,它将对数组的每个元素执行值初始化,对于`double`,这将导致它们被零初始化,如果您使用`vect_{}`,它将使用空初始化程序列表执行聚合初始化,这将导致相同的每个元素值初始化。 - JAB
当然,如果T是具有任何类型的显式构造函数,则值将被默认初始化(如果T没有提供其成员的默认初始化程序的默认构造函数,则可能导致T的未初始化成员)。 - JAB
当然,你是对的 - 抱歉。不管怎样,幸运的是,启用优化后编译器(至少是最近的g++/clang++)不会输出无用的代码。 - manlio

1

一个生成filled_array的函数应该省略其返回值:

template<unsigned N, typename T>
std::array<T, N> filled_array_sized( T const& t ) {
  std::array<T, N> retval;
  retval.fill( t );
  return retval;
}

但这需要至少传入大小N,如果不是类型T

template<typename T>
struct array_filler {
  T && t;
  template<typename U, unsigned N>
  operator std::array<U, N>()&& {
    return filled_array_sized<N, U>( std::forward<T>(t) );
  }
  array_filler( T&& in ):t(std::forward<T>(in)) {}
};
template<typename T>
array_filler< T >
filled_array( T&& t ) {
  return array_filler<T>( t );
}

请注意,将 filled_array 的返回值存储在 auto 中不被建议。

使用以下方式:

#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v): vect_( filled_array( std::move(v) ) ) {
    //...
  }
//...

我不知道上述代码在实现filled_array_size时是否会产生警告,但如果确实出现了,可以在本地禁用该警告。

你可以通过利用 fill 接受 const T& 这一事实来简化这个过程;当元素将被复制时,进行所有转发并没有什么意义。 - Stuart Olsen
@StuartOlsen 是的,但是 fill 最终可以通过添加一个 rvalue 版本来进行改进,该版本将复制前 N 个并将其 move 到最后一个!... 好吧好吧,这相当微不足道。 - Yakk - Adam Nevraumont
@StuartOlsen 我利用了 const&,但由于 array 和初始化类型之间的类型不兼容(在 filled_array 的情况下我无法知道初始化类型,因为我们直到后来才能访问数组类型),所以我必须进行转发操作,直到达到 filled_array_sized。请注意,上述内容广泛依赖于 RVO 和 NRVO 以保持高效。 - Yakk - Adam Nevraumont
对于我的目的来说,简单的filled_array_sized已经足够了(使用RVO时,它与其他解决方案一样有效,并且似乎更符合Effective C++的精神)。无论如何,这篇文章非常有教育意义。 - manlio

1
这是另一种方法,我认为更加简洁,使用C++11非静态数据成员初始化
#include <array>

template<class T, unsigned N>
class fitness
{
public:
  explicit fitness(T v)
  {
    static_assert(N, "fitness zero length");

    vect_.fill(v);
  }

private:
  std::array<T, N> vect_ { };
};

int main()
{
  fitness<double, 4> f(-1000.0);

  return 0;
}

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