1

I am trying to create an array of X pointers referencing matrices of dimensions Y by 16. Is there any way to accomplish this in C++ without the use of triple pointers?

Edit: Adding some context for the problem.

There are a number of geometries on the screen, each with a transform that has been flattened to a 1x16 array. Each snapshot represents the transforms for each of number of components. So the matrix dimensions are 16 by num_components by num_snapshots , where the latter two dimensions are known at run-time. In the end, we have many geometries with motion applied.

I'm creating a function that takes a triple pointer argument, though I cannot use triple pointers in my situation. What other ways can I pass this data (possibly via multiple arguments)? Worst case, I thought about flattening this entire 3D matrix to an array, though it seems like a sloppy thing to do. Any better suggestions?

What I have now:

function(..., double ***snapshot_transforms, ...)

What I want to accomplish:

function (..., <1+ non-triple pointer parameters>, ...)

Below isn't the function I'm creating that takes the triple pointer, but shows what the data is all about.

static double ***snapshot_transforms_function (int num_snapshots, int num_geometries)
{
    double component_transform[16];

    double ***snapshot_transforms = new double**[num_snapshots];

    for (int i = 0; i < num_snapshots; i++)
    {
        snapshot_transforms[i] = new double*[num_geometries];

        for (int j = 0; j < num_geometries; j++)
        {
            snapshot_transforms[i][j] = new double[16];

            // 4x4 transform put into a 1x16 array with dummy values for each component for each snapshot
            for (int k = 0; k < 16; k++)
                snapshot_transforms[i][j][k] = k;
        }
    }

    return snapshot_transforms;
}

Edit2: I cannot create new classes, nor use C++ features like std, as the exposed function prototype in the header file is getting put into a wrapper (that doesn't know how to interpret triple pointers) for translation to other languages.

Edit3: After everyone's input in the comments, I think going with a flattened array is probably the best solution. I was hoping there would be some way to split this triple pointer and organize this complex data across multiple data pieces neatly using simple data types including single pointers. Though I don't think there is a pretty way of doing this given my caveats here. I appreciate everyone's help =)

Ash
  • 21
  • 4
  • 4
    Yes. That was easy! – Yakk - Adam Nevraumont Feb 28 '19 at 03:04
  • "*Is there a way*" - Yes. HOW, you ask? That is a different question you didn't ask. It would also help if you would show the code you have that you don't like, and someone will show you a better way to handle it. – Remy Lebeau Feb 28 '19 at 03:10
  • @Yakk-AdamNevraumont Ha! Answer accepted. – Ash Feb 28 '19 at 05:01
  • @RemyLebeau I've added some code to show the gist of what I'm trying to accomplish. – Ash Feb 28 '19 at 05:01
  • @Ash That looks like the proper way to do it. – dbush Feb 28 '19 at 05:10
  • This answer may be what you're after. https://stackoverflow.com/questions/53038457/what-is-the-best-modern-c-approach-to-construct-and-manipulate-a-2d-array/53038618#53038618 The question might have been a duplicate but it was closed. – Galik Feb 28 '19 at 06:08
  • @Galik Thanks for helping me search around. Though, I need to accomplish this without using any functions offered from std. – Ash Feb 28 '19 at 07:01
  • @Ash - for our edification, why is it you `"cannot use triple pointers?"` (other than form and "don't want to"?) If you have in effect a 3D array, you will have 3-levels of indirection. Is there a limitation of the language this is getting put into? – David C. Rankin Feb 28 '19 at 07:07
  • @DavidC.Rankin Hi David. This is an API that is getting thrown into a wrapper to be converted to other languages, and this wrapper I'm restricted to using does not know how to interpret triple pointers. So, I'm trying to figure out how allow a user to pass this 3D data to my function without the use of a triple-pointer-argument. The simplest solution I can think of would be for the user to pass in a single, flattened array of all this data. I'm wondering if there is a cleaner way for them to pass this data using 1+ arguments in place of the triple pointer. – Ash Feb 28 '19 at 07:12
  • @Ash - makes perfect sense, then it looks like the best approach is to flatten and write a helper to map indexes. – David C. Rankin Feb 28 '19 at 07:39
  • "this wrapper I'm restricted to using does not know how to interpret triple pointers." This doesn't look like making a lot of sense. Either it accepts pointers and doesn't care about their type (i.e. `void*`) and then it doesn't really matter if you give it triple or quintuple pointers, all of them are equally good at being `void*`. Or it accepts pointers of a specific type, and then you don't have any freedom in choosing your representation, you have to do what your wrapper expects. – n. m. could be an AI Feb 28 '19 at 09:57
  • "this wrapper I'm restricted to using does not know how to interpret triple pointers." -- I am VTC as your question does not include the information needed to answer it; namely, what that wrapper is and what exactly it can or cannot do. – Yakk - Adam Nevraumont Feb 28 '19 at 11:16

2 Answers2

2

It is easier, better, and less error prone to use an std::vector. You are using C++ and not C after all. I replaced all of the C-style array pointers with vectors. The typedef doublecube makes it so that you don't have to type vector<vector<vector<double>>> over and over again. Other than that the code basically stays the same as what you had.

If you don't actually need dummy values I would remove that innermost k loop completely. reserve will reserve the memory space that you need for the real data.

#include <vector>

using std::vector; // so we can just call it "vector"
typedef vector<vector<vector<double>>> doublecube;

static doublecube snapshot_transforms_function (int num_snapshots, int num_geometries)
{    
    // I deleted component_transform. It was never used

    doublecube snapshot_transforms;
    snapshot_transforms.reserve(num_snapshots);

    for (int i = 0; i < num_snapshots; i++)
    {
        snapshot_transforms.at(i).reserve(num_geometries);

        for (int j = 0; j < num_geometries; j++)
        {
            snapshot_transforms.at(i).at(j).reserve(16);

            // 4x4 transform put into a 1x16 array with dummy values for each component for each snapshot
            for (int k = 0; k < 16; k++)
                snapshot_transforms.at(i).at(j).at(k) = k;
        }
    }

    return snapshot_transforms;
}
  • 1
    Don't nest vector like this, your data locality will be ridiculously bad. Instead flatten the matrix to one dimension (`vector`) and provide a helper for converting from 3D coordinates to flat indexes. – Ben Voigt Feb 28 '19 at 05:54
  • @Dustin Thanks for the suggestion, though I have to agree with Ben, and I'd rather flatten the 3D matrix than nest vectors. I didn't mention in my OP, but I cannot utilize the many features of C++ here like the std libraries as this bit I'm working on will be thrown into a wrapper and converted to other languages. – Ash Feb 28 '19 at 06:50
1

Adding a little bit of object-orientation usually makes the code easier to manage -- for example, here's some code that creates an array of 100 Matrix objects with varying numbers of rows per Matrix. (You could vary the number of columns in each Matrix too if you wanted to, but I left them at 16):

#include <vector>
#include <memory>  // for shared_ptr (not strictly necessary, but used in main() to avoid unnecessarily copying of Matrix objects)

/** Represents a (numRows x numCols) 2D matrix of doubles */
class Matrix
{
public:
   // constructor
   Matrix(int numRows = 0, int numCols = 0)
      : _numRows(numRows)
      , _numCols(numCols)
   {
      _values.resize(_numRows*_numCols);
      std::fill(_values.begin(), _values.end(), 0.0f);
   }

   // copy constructor
   Matrix(const Matrix & rhs)
      : _numRows(rhs._numRows)
      , _numCols(rhs._numCols)
   {
      _values.resize(_numRows*_numCols);
      std::fill(_values.begin(), _values.end(), 0.0f);
   }

   /** Returns the value at (row/col) */
   double get(int row, int col) const {return _values[(row*_numCols)+col];}

   /** Sets the value at (row/col) to the specified value */
   double set(int row, int col, double val) {return _values[(row*_numCols)+col] = val;}

   /** Assignment operator */
   Matrix & operator = (const Matrix & rhs)
   {
      _numRows = rhs._numRows;
      _numCols = rhs._numCols;
      _values  = rhs._values;
      return *this;
   }

private:
   int _numRows;
   int _numCols;
   std::vector<double> _values;
};

int main(int, char **)
{
   const int numCols = 16;
   std::vector< std::shared_ptr<Matrix> > matrixList;
   for (int i=0; i<100; i++) matrixList.push_back(std::make_shared<Matrix>(i, numCols));

   return 0;
}
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • Your example has nothing to do with OO. – n. m. could be an AI Feb 28 '19 at 05:29
  • 2
    @n.m. it sounds like you don't quite understand the scope of O.O, and think it applies to a narrower range of designs than it actually does. – Jeremy Friesner Feb 28 '19 at 05:31
  • It sounds like you don't quite understand the scope of O.O, and think it applies to a broader range of designs than it actually does. Creating a class is not OO. STL is not OO. Ask Alex Stepanov. – n. m. could be an AI Feb 28 '19 at 05:36
  • 2
    "Encapsulation is one of the fundamentals of OOP (object-oriented programming). It refers to the bundling of data with the methods that operate on that data." https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) – Jeremy Friesner Feb 28 '19 at 05:38
  • "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme *late binding* of all things" (Alan Kay, the inventor of the term). – n. m. could be an AI Feb 28 '19 at 05:50
  • "STL is not object oriented" (Alex Stepanov). According to you, std::vector is OO. Whom do I believe? – n. m. could be an AI Feb 28 '19 at 05:57
  • @JeremyFriesner Thanks for the suggestion, though for my use case I am not particularly looking for an oo approach. I'm exposing an API with a triple pointer parameter (a parameter which I'm trying to decompose), and internally writing some autotests for maintenance of this API (and should not be creating new classes in doing this). – Ash Feb 28 '19 at 06:54