How do you declare and create a 2-dimensional array whose dimensions are constant and known at compile time, but are specified by literal const
arguments to the constructor of the class that owns it?
For example...
Foo.h:
class Foo {
public:
Foo(int rows, int cols);
private:
int totalRows;
int totalCols;
char buf[4][20]; // I don't actually WANT to hardcode 4 and 20 here!
};
Foo.cpp:
Foo::Foo(const int rows, const int cols) : totalRows(rows), totalCols(cols), buf(new char[rows][cols]) {}
Main.cpp:
Foo myFoo(4,20);
I know buf(char[rows][cols])
is totally wrong... it's just there to illustrate what I'm trying to achieve.
I'm pretty sure I somehow have to use constructor-initialization syntax (like I'm using to set the values of totalRows
and totalCols
) ... but, for arrays, I'm not sure what that syntax actually is. I'm not sure whether such syntax even exists for array-declaration, since the use case of "constructor with const int args that can only be invoked with literals to guarantee the values are known at compile-time and thus suitable for an array declaration" is admittedly kind of an extreme edge case.
In Java, I'd just declare a char[]
named buf
whose value is implicitly unassigned at declaration-time, then create (and assign) it in the body of the constructor ... but, as far as I know, C doesn't allow that, either.
I know I could probably sidestep the issue by making buf
a char*
and creating it in the constructor via malloc(rows * cols)
... but that seems kind of barbaric and just seems "bad" for some reason I can't quite put my finger on.
CodePudding user response:
You could make Foo
a class template, then a separate class will be created by the compiler for each different combination of rows
and cols
that are used. This would avoid the need for any initialization in the constructor (for the simple class that your have outlined), as all data members could be default initialized to their desired values.
The visible difference in your main
would be, rather than declaring and initializing with syntax like Foo myFoo(4, 20);
, you would use Foo<4, 20> myFoo
… which would create myFoo
as an object of the class called (something like) Foo<4,20>
.
Here's a short outline:
#include <iostream>
template<int rows, int cols>
class Foo {
public:
Foo() {}
private:
int totalRows{ rows };
int totalCols{ cols };
char buf[rows][cols];
};
int main()
{
Foo<4, 20> myFoo;
//...
}
CodePudding user response:
Use a vector of vectors.
#include <cassert>
#include <vector>
#include <stdexcept>
class Foo
{
public:
// with std::vector you can dynamically allocate
// a 2D array of the size you want.
Foo(std::size_t rows, std::size_t cols) :
m_buffer(rows, std::vector<int>(cols, 0)) // initialize all rows to 0
{
if ((rows == 0) || (cols == 0)) throw std::invalid_argument("invalid input size");
}
std::size_t number_of_rows() const
{
return m_buffer.size();
}
std::size_t number_of_columns() const
{
return m_buffer[0].size();
}
int at(std::size_t row, std::size_t column)
{
if ((row >= number_of_rows()) || (column >= number_of_columns())) throw std::invalid_argument("invalid index");
return m_buffer[row][column];
}
private:
std::vector<std::vector<int>> m_buffer;
};
int main()
{
Foo foo{ 3,4 };
assert(foo.number_of_rows() == 3ul);
assert(foo.number_of_columns() == 4ul);
assert(foo.at(1, 1) == 0);
return 0;
}