2

I am creating a C++ class which wraps an floating point 2d array and provides some additional functionality. I want to pass the array's shape as parameters to the constructor, see the code (the class Block part) below. The line ends with comment "// here" would cause error during compilation because _nx and _ny are not known at that time. There are two solutions (I think) around this: one is using pointer (see solution 1 in the code below) and dynamically allocate the array; the other is using template (see solution 2 in the code below), but I have several reasons not to use them:

  1. I don't want to use pointer as long as there is a pointer-less option; in other words, I don't want to use new and delete. The reason for this is a personal preference for purer c++.
  2. I don't want to use template because there can be many different block shapes - I don't want the compiler to create many classes for each of them, this is an overkill and increases the executable size.

In addition, I don't want to use stl vector because the array size is fixed after creation; also I am doing numerical computation, so a 'raw' array suits me much better.

I have searched in SO and there are five or six questions asking similar problems, there is no conclusion which one is better though, and none of them are from a numerical standing point so vector or new/detele are good answers for them - but not for me. Another reason I post this question is I want to know if I am too restrictive in using c++ features. As I will be using c++ extensively, it's very important to be aware of c++'s limitation and stop asking/searching too much for some feature that doesn't exist.

#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;
}
Taozi
  • 373
  • 5
  • 13
  • 1
    With a good optimizing compiler, `std::vector` is no slower than raw arrays provided you don't resize them. – Brian Bi May 16 '14 at 20:15
  • possible duplicate of [Declare array of fixed length in nested classes](http://stackoverflow.com/questions/23496273/declare-array-of-fixed-length-in-nested-classes) – gsamaras May 16 '14 at 20:17
  • I dont's see why not using pointers is "purer". But I think the pointer solution is preferable to vectors if you really can take advantage of not using `std::vector` (which is no slower than war awways in modern compilers given that you use optimisation flags) – Mateo Torres May 16 '14 at 20:20

4 Answers4

4

Just use a std::vector. I had the same decision problem a week before and had asked here.

If you use reserve(), which shall not make your vector reallocate itself many times(if any), then vectors are not going to influence the performance of your project. In other words, vector is unlikely to be your bottleneck.

Notice, that in C++ vectors are used widely, thus in release mode, the optimizations made to them are really efficient.

Or wait for std::dynarray to be introduced! (Unfortunately not in C++14, but in array TS or C++17). Source, credits to manlio.

Never forget: Premature optimization is the source of Evil. - Knuth.

Don't believe me? You shouldn't! Experiment yourself and find out!

Here is my experiment to get me convinced when I had exactly the same question as you.

Experiment code:

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

Results (notice the -o2 compiler flag):

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.

So, just a std::vector. :) I am pretty sure you know how to change your code for that and you do not need me to tell you (is so, let me know of course :) ).

You can try with other time methods, found in my pseudo-site.

Community
  • 1
  • 1
gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • On my laptop the push_back method seems either as fast or faster compared with emplace_back (which I didn't know before). The array version takes ~1E-6s while the vector version takes ~1E-2s for N=1E6. I've been googling after posting my question, seems that vector is not good for numerical computation, one reason is it does not parallelize well. You mentioned dynarray, I think the reason it's going into the standard is because C++ lacks such a feature people like me need. I do miss Fortran array, certainly for more complex data structure C++ quickly shows its power. I will use 'raw' array. – Taozi May 17 '14 at 21:05
  • @Taozi *like we need! The approach I suggest you to follow is this: Complete your project. If you feel that you can do better, make a version of the project which uses an array. Then measure. You see, time measurements, it's what happening in practise, and it's really crucial, so bravo for going into the habit (+1 to your question). – gsamaras May 17 '14 at 22:18
  • @Taozi, I asked about `push_back` and `emplace_back` here http://stackoverflow.com/questions/23717151/why-emplace-back-is-faster-than-push-back?noredirect=1#comment36450935_23717151, if you are interested. – gsamaras May 17 '14 at 23:38
  • Unfortunately, it seems that `dynarray` isn't going to be in C++14: http://stackoverflow.com/questions/20777623/what-is-the-status-on-dynarrays – manlio May 19 '14 at 08:24
  • @manlio that's important info. I shall update this answer and another relevant question I had made. – gsamaras May 19 '14 at 11:32
1

I think you're being overly cautious in rejecting std::vector just because of the resizability issue. Surely your program can accommodate sizeof(Block) being a few of pointer sizes larger than the raw pointer solution. A vector for maintaining the matrix should be no different than your pointer solution as far as performance goes, if you use a single vector instead of a vector of vectors.

Using vector will also make it a lot more unlikely that you'll screw up. For instance, your auto_ptr solution has undefined behavior because the auto_ptr is going to delete, instead of delete[], the array in the destructor. Also, you most likely won't get the behavior you expect unless you define a copy constructor and assignment operator.

Now, if you must eschew vector, I'd suggest using unique_ptr instead of 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
};

This will correctly call delete[] and it won't transfer ownership of the array as readily as auto_ptr.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Yes, auto_ptr is dying and I will use unique_ptr, also I will disable copy constructor and assign operator because ownership is never transferred in my design. – Taozi May 17 '14 at 21:09
  • @Taozi You won't need to do so explicitly if you have a `unique_ptr` data member. It's presence is enough for the copy constructor & copy assignment operator to be implicitly deleted. – Praetorian May 17 '14 at 22:47
0

Memory is cheap these days, and your block matrices are very very small.

So, when you don't want to use templates, and don't want to use dynamic allocation, well just use a fixed size array sufficiently large for the largest possible block.

It's that simple.


The code you show with std::auto_ptr has two main problems:

  • std::auto_ptr is deprecated in C++11.

  • std::auto_ptr always performed a delete p, which yields Undefined Behavior when the allocation was of an array, like new T[n].


By the way, regarding the envisioned code bloat with templates, you may be pleasantly surprised if you measure.


Also in passing, this smells quite a bit of premature optimization. With C++ it's a good idea to always keep performance in mind, and not do needlessly slow or memory consuming things. But also, a good idea to not get bogged down in needlessly working around some perceived performance problem that really doesn't matter, or wouldn't have mattered if it were just ignored.

And so, your main default choice should be to use a std::vector for the storage.

Then, if you suspect that it's too slow, measure. The release version. Oh I've said that only twice, so here's third: measure. ;-)

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Hi Alf, thank you for your answer. Regarding the array size, it could be large and there can be several of them, for example, 100x100 and 200x50, sorry for the misleading array size in the example code. I understand the premature thing, as I said, I want to be more clear about the tool in my hand -- esp. its limitation, so in the future I don't have to wonder. In a word, this should be the last time I ask questions of this kind :-) – Taozi May 16 '14 at 20:29
0

std::vector is your friend, no need to rebuild the 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
};

You may also use a simple double* as in your first solution. Do not forget to delete it when destroying the block though.

agrum
  • 407
  • 2
  • 16