How to make a class with array member size determined via constructor?

Pages: 12
I am trying to make a class with a 2D array, the length of which is given in parameters of the constructor, so basically when I create an object called Screen, of the class CharMap, I want it to look like this:
new CharMap Screen (/*any number*/, /*any number*/) and the two numbers are the length and width of the array. This is the code I wrote, but the damn compiler won't compile it because error: invalid use of non-static data member 'CharMap::yres'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  class CharMap
{
    int xres, yres;
    char CharGrid[yres][xres];
    public:
    CharMap(int, int);
    char GetChar (int getx, int gety)
    {
        return CharGrid[gety-1][getx-1];
    };
    void WriteChar(int writex, int writey, char writechar)
    {
        CharGrid[writey-1][writex-1] = writechar;
    }
    void ClearMap()
    {
        for (int clry=0; clry<=(yres-1); clry++)
        {
            for (int clrx=0; clrx<=(xres-1); clrx++)
            {
                CharGrid[clry-1][clrx-1] = 'X';
            };
        };
    };
    void PrintMap()
    {
        system("CLS");
        for (int printy=0; printy <= (yres-1); printy++)
        {
            for (int printx=0; printx <= (xres-1); printx++)
            {
                cout << CharGrid[printy][printx];
            };
            cout << "\n";
        };
    };
};

CharMap::CharMap (int x, int y)
{
    xres = x;
    yres = y;
    
};


I am not quite sure what the problem is. What am I doing wrong and how can I make this class do what I want it to do?
In C++ an array is not able to have a dynamic size using stack memory. If you want to create an array from a constructor you have to allocate it with dynamic/heap memory.

http://www.cplusplus.com/doc/tutorial/dynamic/

However in most cases where you want to use dynamic arrays you might as well just use vectors. (Which can also be 2 dimensional).
http://www.cplusplus.com/reference/vector/vector/
The official "C++ Language Tutorial" doesn't cover vectors, and I don't have a programming teacher; Do you know where I can find a good tutorial for vectors?
Last edited on
I can't seem to find anything that explains how to create a two dimensional vector.

Also, I'm not sure a vector is the right type for this situation. My understanding is that the whole point of vectors is to be able to shrink or grow dynamically, but the grid I am trying to create will never need to change size for any reason.
Last edited on

You could dynamically allocate using the new operator like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

	// first number/second number are sizes
	// like you would declare a normal array
	// i.e. [1][3]

	int **matrix;

	matrix = new int*[ firstnumber ];  // array of pointers to int objects
	for (int i = 0; i<firstnumber; ++i)
		matrix[i] = new int[ secondnumber ];

	// now you can just access it like any other array
	// such as matrix[1][3]


So in your class private area declare int **matrix; and allocate the memory as required in your constructor
closed account (j3Rz8vqX)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//main.cpp
#include <iostream>
using namespace std;

int main()
{
    int x=0;
    cout<<"What is the size: ";
    cin >> x;

    //Create the array;
    int **row = new int*[x];
    for (int i=0;i<x;i++)
    {
        row[i] = new int[x];
    }

    //Destroy the array
    for (int i=0;i<x;i++)
    {
        delete []row[i];
    }
    delete []row;

	return 0;
}
I am not very good with pointers yet, how would I access my array elements if I use that method?
closed account (j3Rz8vqX)
The same means. It's simply a dynamic 2d array.
1
2
    row[0][5] = 5;
    cout<<row[0][5]<<endl;

Other than construction, destruction, and changing targets, they act the same similar.
Last edited on
This is my whole code. It now crashes and throws "std::bad_alloc" on execution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

#include <iostream>
#include <stdlib.h>
#include <ctime>
using namespace std;

class CharMap
{
    int xres;
    int yres;
    char **CharGrid = new char*[xres];
    public:
    CharMap(int, int);
    char GetChar (int getx, int gety)
    {
        return CharGrid[gety-1][getx-1];
    };
    void WriteChar(int writex, int writey, char writechar)
    {
        CharGrid[writey-1][writex-1] = writechar;
    }
    void ClearMap()
    {
        for (int clry=0; clry<=(yres-1); clry++)
        {
            for (int clrx=0; clrx<=(xres-1); clrx++)
            {
                CharGrid[clry-1][clrx-1] = 'X';
            };
        };
    };
    void PrintMap()
    {
        system("CLS");
        for (int printy=0; printy <= (yres-1); printy++)
        {
            for (int printx=0; printx <= (xres-1); printx++)
            {
                cout << CharGrid[printy][printx];
            };
            cout << "\n";
        };
    };
};

CharMap::CharMap (int x, int y)
{
    xres = x;
    yres = y;
    for (int i=0; i<xres; i++)
    {
        CharGrid[i] = new char[xres];
    }
};

int main ()
{
    CharMap Screen(20, 10);
    Screen.PrintMap();
    cout << 1;
    cin.get();
    Screen.WriteChar(3, 2, 'X');
    Screen.PrintMap();
    cout << 2;
    cin.get();
    return 0;
};
char **CharGrid = new char*[xres];

In combination with

1
2
3
4
5
6
7
8
9
CharMap::CharMap (int x, int y)
{
    xres = x;
    yres = y;
    for (int i=0; i<xres; i++)
    {
        CharGrid[i] = new char[xres];
    }
}


leads to undefined behavior. xres has an indeterminate value when it is fed to new.

Using std::vector (and not much error checking):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <vector>

class CharMap
{
    std::vector<std::vector<char>> _map;
public:
    CharMap(unsigned xDim, unsigned yDim) : _map(yDim, std::vector<char>(xDim, ' ')) {}

    char at(unsigned x, unsigned y) const { return _map[y-1][x-1]; }
    char& at(unsigned x, unsigned y) { return _map[y-1][x-1]; }

    unsigned xDim() const { return _map[0].size(); }
    unsigned yDim() const { return _map.size(); }


    void clear()
    {
        for (auto & row : _map)
            for (auto & cell : row)
                cell = 'X';
    }

    void print() const
    {
        std::system("CLS");

        for (auto& row : _map)
        {
            for (auto& cell : row)
                std::cout << cell;

            std::cout << '\n';
        }

    }
};


int main()
{
    CharMap screen(20, 10);
    screen.print();
    std::cout << 1;
    std::cin.get();


    screen.at(3, 2) = 'X';
    screen.print();
    std::cout << 2;
    std::cin.get();
}




xres has an indeterminate value when it is fed to new


But xres is assigned a value immediately before feeding it to new, how is that indeterminate?
closed account (j3Rz8vqX)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <stdlib.h>
#include <ctime>
using namespace std;

class CharMap
{
    int xres;
    int yres;
    char **CharGrid;
    public:
    CharMap(int, int);
    ~CharMap()
    {
        FreeMapMemory();//Calls the function to release memory
    }
    void FreeMapMemory()//Release memory whenever you're planning to reassign CharGrid to a new CharGrid or simply destruction
    {
        for(int i=0;i<yres;i++)
        {
            delete []CharGrid[i];//Release the array of columns @ array[i][ALL];
        }
        delete []CharGrid;//Release the array of rows @ array[ALL];//Will be exclusive of sub new objects, thus we've deleted prior to this.
        cout<<"New memory was *ideally* freed, unless reassigned before this process"<<endl;
    }
    char GetChar (int getx, int gety)
    {
        return CharGrid[gety-1][getx-1];
    };
    void WriteChar(int writex, int writey, char writechar)
    {
        CharGrid[writey-1][writex-1] = writechar;
    }
    void ClearMap()
    {
        for (int clry=0; clry<=(yres-1); clry++)
        {
            for (int clrx=0; clrx<=(xres-1); clrx++)
            {
                CharGrid[clry-1][clrx-1] = 'X';
            };
        };
    };
    void PrintMap()
    {
        system("CLS");
        for (int printy=0; printy <= (yres-1); printy++)
        {
            for (int printx=0; printx <= (xres-1); printx++)
            {
                cout << CharGrid[printy][printx];
            };
            cout << "\n";
        };
    };
};

CharMap::CharMap (int x, int y)
{
    xres = x;
    yres = y;
    CharGrid = new char*[y];
    for (int i=0; i<y; i++)
    {
        CharGrid[i] = new char[x];
        for(int j=0;j<x;j++)//Filling the array [i][j];j++
        {
            CharGrid[i][j] = '.';//defaulting a character; else garbage on read.
        }
    }
};

int main ()
{
    CharMap Screen(20, 10);
    Screen.PrintMap();
    cout << 1;
    cin.get();
    Screen.WriteChar(3, 2, 'X');
    Screen.PrintMap();
    cout << 2;
    cin.get();
    return 0;
};


It takes time to understand, work it out and ask questions.

You should not declare a new variable array unless you know its definite sizes. In the function declaration, xres and yres has no definite value. So leave it as a blank as illustrated. Assign it a new data after you've determine xres and yres.

Must you declare and delete it this way?

Yes, we're literally declaring a pointer of pointers*; where the pointers* are pointing to array of characters.

When you destroy the object, you must release each new array within your array; the pointer is pointing to pointers - release the embedded first.

Edit: Comment on why I included FreeMemory method.
Last edited on
FWIW, I really dislike the "nested new" approach to this problem. It's clunky to initialize/teardown uses more memory, and is typically [ever so slightly] slower.

Manual memory management is also something you should avoid. My general rule is... if you are manually deleting, you're doing it wrong.


You can use a 1D vector to hold a 2D array. All you have to do is simulate the 2nd dimension with some basic math.

Here's a very simplistic class that should suffice. It's const correct, templated, and copyable, and properly encapsulated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <typename T>
class array2D
{
private:
    std::vector<T> data;
    unsigned wd, ht;

public:
    unsigned width() const { return wd; }
    unsigned height() const { return ht; }

    const T& operator () (unsigned x, unsigned y) const { return data[(y*wd)+x]; }
    T& operator () (unsigned x, unsigned y) { return data[(y*wd)+x]; }

    void resize(unsigned w, unsigned h)
    {
        data.resize(w*h);
        wd = w;
        ht = h;
    }

    array2D() : wd(0), ht(0) { }

    array2D(unsigned x, unsigned y)
        : wd(x), ht(y)
        , data(x*y, val)
    { }
};


Usage example:
1
2
3
4
5
6
7
8
9
10
11
// make an array that is 16x7  (that is, width of 16, height of 7)
array2D<char> foo(16,7);

// resize the array to something else.... let's say 10x4
foo.resize(10,4);

// assign the element at x=4, y=2 to '@'
foo(4,2) = '@';

// print out that element:
std::cout << foo(4,2);
But xres is assigned a value immediately before feeding it to new, how is that indeterminate?


It is not assigned a value immediately before feeding it to new. It is assigned a value before feeding it to new a number of other times, but not prior to the first, which occurs before the body of the constructor is entered.
It is not assigned a value immediately before feeding it to new. It is assigned a value before feeding it to new a number of other times, but not prior to the first, which occurs before the body of the constructor is entered.


But when I create a CharMap object the first thing that happens is the constructor is called. The very first thing that the constructor does is assign xres and yres, so I thought that would make their values known before everything else in the object is created? I thought that was what constructors where for?

Disch, I would need to make that array2D class in addition to my CharMap class so I could then use it in CharMap?
Last edited on
Here's a very simplistic class that should suffice. It's const correct, templated, and copyable, and properly encapsulated:


While we're at it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

template <typename T, size_t W >
class Row {
  T span[ W ];

  public:
  T& operator[]( const size_t idx )
  {
    if ( idx >= W ) throw "row out of range\n";
    return span[ idx ];
  }

  // requires copy constructor etc.
};


const size_t height = 10;
const size_t width  = 10;
using Grid = Row< Row< int, width >, height >;

int main( int argc, char* argv[] )
{
  Grid my_grid;
  try
  {
    for ( size_t i = 0; i < height; ++i )
      for ( size_t j = 0; j < width; ++j )
        my_grid[ i ][ j ] = ( i * 100 ) + j;

    std::cout << my_grid[0][0] << std::endl;
    std::cout << my_grid[5][0] << std::endl;
    std::cout << my_grid[2][6] << std::endl;
    std::cout << my_grid[10][6] << std::endl;
  }
  catch ( const char* error )
  {
    std::cout << error << std::endl;
  }
  return 0;
}

/*
const size_t depth = 10;
using Cube = Row< Grid, depth >;

Cube my_cube;
my_cube[1][2][3] = whatever
*/
Last edited on
> The very first thing that the constructor does is assign xres and yres

The very first thing that happens in your constructor does is field initialisation.
char **CharGrid = new char*[xres];
At that point, xres is uninitialized.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

int init( const char* var, int value )
{ std::cout << "initialise " << var << " with " << value << '\n' ; return value ; }

int assign( const char* var, int value )
{ std::cout << "assign " << value << " to " << var << '\n' ; return value ; }

struct A
{
    int a = init( "A::a", b + 66 ) ; // *** bad *** b is used before it is initialised
    int b = init( "A::b", 88 ) ;
    int c = init( "A::c", 10 ) ;

    A() // member initialisation in order of declaration ie. first a, then b, then c
    {
        std::cout << "in A::constructor body\n" ;
        a = assign( "A::a", b + 66 ) ; // fine
        b = assign( "A::b", 123 ) ;
        c = assign( "A::c", 0 ) ;
    }
};

int main ()
{
    A a ;
}

clang++ -std=c++11 -stdlib=libc++ -O2 -Wall -Wextra -pedantic-errors main.cpp -lsupc++ && ./a.out
main.cpp:11:27: warning: field 'b' is uninitialized when used here [-Wuninitialized]
    int a = init( "A::a", b + 66 ) ; // *** bad *** b is used before it is initialised
                          ^
main.cpp:15:5: note: during field initialization in this constructor
    A() // member initialisation in order of declaration ie. first a, then b, then c
    ^
1 warning generated.
initialise A::a with 66
initialise A::b with 88
initialise A::c with 10
in A::constructor body
assign 154 to A::a
assign 123 to A::b
assign 0 to A::c

http://coliru.stacked-crooked.com/a/83d15a5e31e1ce78
Disch, can I make that 2D vector class into a header that I can use for anything?
R3B3LCAUSE wrote:
Disch, can I make that 2D vector class into a header that I can use for anything?


Yes
Pages: 12