繁体   English   中英

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

[英]Array Size as Constructor Parameter

我正在创建一个包装浮点2d数组并提供一些其他功能的C ++类。 我想将数组的形状作为参数传递给构造函数,请参见下面的代码(类Block部分)。 该行以注释“ // here”结尾,这会在编译期间导致错误,因为当时不知道_nx和_ny。 有两种解决方案(我认为):一种是使用指针(请参见下面的代码中的解决方案1)并动态分配数组;另一种是使用指针。 另一个正在使用模板(请参见下面的代码中的解决方案2),但是我有几个不使用它们的原因:

  1. 只要有无指针选项,我就不想使用指针。 换句话说,我不想使用new和delete。 这样做的原因是个人对纯c ++的偏爱。
  2. 我不想使用模板,因为可以有很多不同的块形状-我不想编译器为它们中的每一个创建很多类,这太过分了,并且增加了可执行文件的大小。

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

我在SO中进行搜索,有五个或六个问题在问类似的问题,虽然没有一个结论是更好的,但都不是从数值的观点出发,因此矢量或new / detele都是它们的好答案-但不是为了我。 我发布此问题的另一个原因是我想知道我在使用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::auto_ptr显示的代码有两个主要问题:

  • 在C ++ 11中不推荐使用std::auto_ptr

  • std::auto_ptr始终执行delete p ,当分配是数组时,如new T[n] ,将产生Undefined Behavior。


顺便说一下,对于预想的带有模板的代码膨胀,如果进行测量 ,您可能会感到惊喜。


顺便说一句,这闻起来还有些过早的优化。 使用C ++时,最好始终牢记性能,而不要不必要地进行缓慢或占用内存的操作。 但是,这也是一个好主意,不要因不必要地解决一些实际无关紧要的性能问题而陷入困境,或者如果只是忽略它就不会有问题。

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

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

std :: vector是您的朋友,无需重建轮子:

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
};

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

我认为您只是因为可调整大小性问题而对std::vector过于谨慎。 当然,您的程序可以容纳的sizeof(Block)比原始指针解决方案大几个指针。 如果使用单个vector而不是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那样容易地转移数组的所有权。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM