【问题标题】:Array Size as Constructor Parameter数组大小作为构造函数参数
【发布时间】:2014-05-16 20:13:44
【问题描述】:

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

  1. 只要有无指针,我就不想使用指针 选项;换句话说,我不想使用 new 和 delete。这 原因是个人偏好更纯粹的 c++。
  2. 我不想要 使用模板,因为可以有许多不同的块形状 - 我 不希望编译器为每个类创建许多类, 这是一种矫枉过正的做法,会增加可执行文件的大小。

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

我在 SO 中搜索过,有五六个问题问类似的问题,但没有结论哪个更好,而且没有一个是从数字立场出发的,所以 vector 或 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 不会比原始数组慢。
  • 我不明白为什么不使用指针是“更纯粹的”。但是我认为如果你真的可以利用不使用std::vector 的优势,那么指针解决方案比向量更可取(考虑到你使用优化标志,这在现代编译器中并不比战争慢)

标签: c++ arrays constructor


【解决方案1】:

只需使用std::vector。一周前我遇到了同样的决定问题,并问过here

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

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

或者等待std::dynarray的介绍! (不幸的是不在C++14,而是在array TSC++17)。 Source,感谢 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。 :) 我很确定你知道如何更改你的代码,你不需要我告诉你(是这样,当然让我知道:))。

您可以尝试其他时间方法,在我的pseudo-site 中找到。

【讨论】:

  • 在我的笔记本电脑上,push_back 方法似乎与 emplace_back (我以前不知道)一样快或更快。数组版本需要~1E-6s,而向量版本需要~1E-2s,N=1E6。发布我的问题后,我一直在谷歌搜索,似乎向量不适合数值计算,原因之一是它不能很好地并行化。你提到了 dynarray,我认为它进入标准的原因是因为 C++ 缺少像我这样的人需要的特性。我确实很想念 Fortran 数组,当然对于更复杂的数据结构,C++ 很快就显示了它的威力。我将使用“原始”数组。
  • @Taozi *就像我们需要的一样!我建议您遵循的方法是:完成您的项目。如果您觉得可以做得更好,请制作一个使用数组的项目版本。然后测量。你看,时间测量,这是在实践中发生的事情,它真的很重要,所以要养成这个习惯(+1 对你的问题)。
  • @Taozi,我问了push_backemplace_back这里stackoverflow.com/questions/23717151/…,如果你有兴趣。
  • 不幸的是,dynarray 似乎不会出现在 C++14 中:stackoverflow.com/questions/20777623/…
  • @manlio 这是重要的信息。我将更新这个答案和我提出的另一个相关问题。
【解决方案2】:

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*。不过在销毁块时不要忘记删除它。

【讨论】:

    【解决方案3】:

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

    因此,当您不想使用模板,也不想使用动态分配时,只需使用一个足够大的固定大小数组来容纳最大可能的块。

    就这么简单。


    您使用std::auto_ptr 显示的代码有两个主要问题:

    • std::auto_ptr 在 C++11 中已弃用。

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


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


    顺便说一句,这听起来有点过早的优化。使用 C++ 时,始终牢记性能是一个好主意,不要做不必要的缓慢或消耗内存的事情。但是,不要陷入不必要地解决一些真正无关紧要的性能问题,或者如果只是忽略它就无关紧要的好主意。

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

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

    【讨论】:

    • 嗨,阿尔夫,感谢您的回答。关于数组大小,它可能很大并且可以有多个,例如 100x100 和 200x50,对于示例代码中的误导性数组大小,请见谅。正如我所说,我理解为时过早的事情,我想更清楚地了解我手中的工具——尤其是。它的局限性,所以将来我不必怀疑。总之,这应该是我最后一次问这种问题了:-)
    【解决方案4】:

    我认为您在拒绝 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 那样容易地转移数组的所有权。

    【讨论】:

    • 是的,auto_ptr 即将死去,我将使用 unique_ptr,我还将禁用复制构造函数和分配运算符,因为所有权在我的设计中从未转移。
    • @Taozi 如果您有unique_ptr 数据成员,则不需要明确这样做。它的存在足以隐式删除复制构造函数和复制赋值运算符。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-14
    • 1970-01-01
    • 1970-01-01
    • 2014-09-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多