数组大小作为构造函数参数

Array Size as Constructor Parameter

本文关键字:构造函数 参数 数组      更新时间:2023-10-16

我正在创建一个c++类,它包装了一个浮点2d数组并提供了一些额外的功能。我想把数组的形状作为参数传递给构造函数,参见下面的代码(类块部分)。以注释"//here"结束的行将在编译过程中导致错误,因为那时_nx和_ny是未知的。有两种解决方案(我认为):一种是使用指针(见下面代码中的解决方案1)并动态分配数组;另一种是使用模板(参见下面代码中的解决方案2),但我有几个理由不使用它们:

  1. 我不想使用指针,只要有一个无指针选择;换句话说,我不想使用new和delete。的这样做的原因是个人对纯c++的偏好。
  2. 我不想要使用模板,因为可以有许多不同的块形状-我不希望编译器为每个类创建许多类,

另外,我不想使用stl vector,因为数组的大小在创建后是固定的;我也在做数值计算,所以一个"原始"数组更适合我。

我在SO中搜索过,有五六个问题问类似的问题,没有结论哪一个更好,但是没有一个是从数值角度来看的,所以矢量或新/删除是他们的好答案-但不适合我。我提出这个问题的另一个原因是我想知道我在使用c++特性方面是否过于严格。由于我将广泛使用c++,因此意识到c++的局限性并停止过多地询问/搜索一些不存在的特性是非常重要的。

#include <iostream>
#include <memory>
using namespace std;
class Block
{
    public:
        Block(int nx, int ny):_nx(nx),_ny(ny){}
        void Report(void)
        {
            cout << "Block With Size ["<<_nx<<","<<_ny<<"]n";
        }
    private:
        const int _nx, _ny;
        double _data[_nx][_ny]; // here
};

/// Solution 1, using auto_ptr
class BlockAuto
{
    public:
        BlockAuto(int nx, int ny):_nx(nx),_ny(ny),_data(new double[_nx*_ny]){}
        void Report(void)
        {
            cout << "BlockAuto With Size ["<<_nx<<","<<_ny<<"]n";
        }
    private:
        const int _nx;
        const int _ny;
        const auto_ptr<double> _data;
};

/// Solution 2, using template
template<unsigned int nx, unsigned int ny>
class BlockTpl
{
    public:
        BlockTpl():_nx(nx),_ny(ny){}
        void Report(void)
        {
            cout << "BlockTpl With Size ["<<_nx<<","<<_ny<<"]n";
        }
    private:
        const int _nx;
        const int _ny;
        double _data[nx][ny]; // uncomfortable here, can't use _nx, _ny
};
int main(int argc, const char *argv[])
{
    Block b(3,3);
    b.Report();
    BlockAuto ba(3,3);
    ba.Report();
    BlockTpl<3,4> bt;
    bt.Report();
    return 0;
}

仅使用std::vector。一周前我也遇到过同样的决策问题,并在这里问过。

如果您使用reserve(),这将不会使您的vector重新分配多次(如果有的话),那么vectors将不会影响您的项目的性能。换句话说,vector不太可能成为您的瓶颈。

注意,在C++vectors被广泛使用,因此在release mode中,对进行的优化是非常有效的。

或者等待std::dynarray的引入!(不幸的是,不是在C++14,但在array TSC++17)。来源,归功于manlio。

永远不要忘记:过早的优化是邪恶的来源。 - Knuth.

不相信我?你不应该!自己尝试一下,找出答案吧!

这是我的实验,当我有和你完全相同的问题时,让我相信。

实验代码:

#include <iostream>
#include <vector>
#include <ctime>
#include <ratio>
#include <chrono>
using namespace std;
int main() {
  const int N = 100000;

  cout << "Creating, filling and accessing an array of " << N << " elements.n";
  using namespace std::chrono;
  high_resolution_clock::time_point t1 = high_resolution_clock::now();
  int array[N];
  for(int i = 0; i < N; ++i)
    array[i] = i;
  for(int i = 0; i < N; ++i)
    array[i] += 5;
  high_resolution_clock::time_point t2 = high_resolution_clock::now();
  duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
  std::cout << "It took me " << time_span.count() << " seconds.";
  std::cout << std::endl;

  cout << "Creating, filling and accessing an vector of " << N << " elements.n";
  t1 = high_resolution_clock::now();
  vector<int> v;
  v.reserve(N);
  for(int i = 0; i < N; ++i)
    v.emplace_back(i);
  for(int i = 0; i < N; ++i)
    v[i] += 5;
  t2 = high_resolution_clock::now();
  time_span = duration_cast<duration<double>>(t2 - t1);
  std::cout << "It took me " << time_span.count() << " seconds.";
  std::cout << std::endl;
  return 0;
}

结果(注意-o2编译器标志):

samaras@samaras-A15:~$ g++ -std=gnu++0x -o2 px.cpp
samaras@samaras-A15:~$ ./a.out
Creating, filling and accessing an array of 100000 elements.
It took me 0.002978 seconds.
Creating, filling and accessing an vector of 100000 elements.
It took me 0.002264 seconds.

所以,只是一个std::vector。:)我很确定你知道如何改变你的代码,你不需要我告诉你(是这样,让我知道当然:))。

你可以尝试其他的时间方法,在我的伪网站上找到。

我认为你在拒绝std::vector时过于谨慎了,只是因为可调整大小的问题。当然,您的程序可以容纳sizeof(Block)比原始指针解决方案大几个指针大小。如果您使用单个vector而不是向量的向量,那么就性能而言,用于维护矩阵的vector应该与指针解决方案没有什么不同。

使用vector也会使你更不可能搞砸。例如,您的auto_ptr解决方案具有未定义的行为,因为auto_ptr将转到delete,而不是析构函数中的数组delete[]。而且,除非定义了复制构造函数和赋值操作符,否则很可能得不到预期的行为。

现在,如果你必须避免vector,我建议使用unique_ptr而不是auto_ptr

class Block
{
    public:
        Block(int nx, int ny):_nx(nx),_ny(ny), _data(new double[_nx*_ny])
        {}
        void Report(void)
        {
            cout << "Block With Size ["<<_nx<<","<<_ny<<"]n";
        }
    private:
        const int _nx, _ny;
        std::unique_ptr<double[]> _data; // here
};

这将正确调用delete[],它不会像auto_ptr那样容易地转移数组的所有权

std::vector是你的朋友,不需要重建wheel:

class Block
{
public:
  BlockAuto(int p_rows, int p_cols):m_rows(nx),m_cols(ny)
  {
    m_vector.resize(m_rows*m_cols);
  }
  double at(uint p_x, uint p_y)
  {
    //Some check that p_x and p_y aren't over limit is advised
    return m_vector[p_x + p_y*m_rows];
  }
  void Report(void)
  {
    cout << "Block With Size ["<<_nx<<","<<_ny<<"]n";
  }
private:
  const int m_rows;
  const int m_cols;
  std::vector<double> m_vector;
  //or double* m_data
};

您也可以像在第一个解决方案中那样使用简单的双*。但是,不要忘记在销毁块时删除它。

现在内存很便宜,你的块矩阵非常非常小。

所以,当你不想使用模板,也不想使用动态分配时,我们就使用一个固定大小的数组,足够大,可以容纳最大的块。

就这么简单。


使用std::auto_ptr显示的代码有两个主要问题:
  • std::auto_ptr在c++ 11中已弃用。

  • std::auto_ptr总是执行delete p,当分配的是数组时,会产生未定义行为,如new T[n]


顺便说一下,关于使用模板所设想的代码膨胀,如果您测量,您可能会感到惊喜。

顺便说一下,这听起来有点不成熟的优化。使用c++时,始终牢记性能是一个好主意,不要做不必要的变慢或消耗内存的事情。但同时,一个好主意是不要陷入不必要的工作中,而这些工作实际上并不重要,或者如果只是忽略它就不会重要。

因此,您的主要默认选择应该是使用 std::vector 作为存储

然后,如果你怀疑它太慢,测量。发布版本。哦,我只说了两次,所以这里是第三个:测量。: -)