I am pretty new to C++ with Boost.
I want an object of class "world" to have an array named "chunk" of type "octreenode". Previously I had an ordinary one-dimensional array, and this worked fine. Now I'm trying to move to using a 3D array with Boost's multi_array functionality, and I'm really not sure what I'm doing wrong.
Simplified code:
class world {
public:
typedef boost::multi_array<octreenode, 3> planetchunkarray; // a boost_multi for chunks
typedef planetchunkarray::index index;
planetchunkarray *chunk;
world(double x,double y,double z,
int widtheast, int widthnorth, int height) :
originx(x), originy(y), originz(z),
chunkseast(widtheast), chunksnorth(widthnorth), chunksup(height) {
chunk = new planetchunkarray(boost::extents[chunksnorth][chunkseast][chunksup]);
planetchunkarray::extent_gen extents;
for (int cz = 0; cz < chunksnorth; ++cz) {
for (int cx = 0; cx < chunkseast; ++cx) {
for (int cy = 0; cy < chunksup; ++cy) {
(*chunk)[cz][cx][cy] = new octreenode(1,72);
}
}
}
}
};
After which if I attempt to make the assignment
root->planet[0]->chunk[0][0][0]->material = 4;
I get the error:
error: base operand of '->' has non-pointer type 'boost::detail::multi_array::sub_array<octreenode, 1u>'|
"octreenode" has the relevant constructor, and this line worked in identical syntax when it was just:
root->planet[0]->chunk[0]->material = 4;
(with a one-dimensional array). Similarly, while it compiled fine with a one-dimensional array, trying to pass the chunk to functions that expect a pointer to an "octreenode" object, such as:
compactoctree(root->planet[p]->chunk[cz][cx][cy], 0, 14);
generates the error
error: cannot convert 'boost::detail::multi_array::sub_array<octreenode, 1u>' to 'octreenode*' for argument '1' to 'short int compactoctree(octreenode*, int, int)'|
Would be very grateful for any suggestions, I'm sure I'm missing something obvious.
Your array is of value type (octreenode), not pointer type (octreenode*)
Therefore you are not supposed to try to assign a pointer to a dynamically allocated octreenode (new is for heap allocation, by default).
Instead, just assign a value:
(*chunk)[cz][cx][cy] = octreenode(1,72);
In fact, there's no reason to use new on the multi array in the first place either:
UPDATE
In the comments it has been raised that more things could be optimized and that you consider that useful additions to the answer about the compilation error.
So here goes: if you indeed want to initialize all array elements with the exact same value,
You can make the loops way more efficient by forgetting about the array shapes for a moment:
std::fill_n(chunk.data(), chunk.num_elements(), octreenode {1, 72});
If you know octreenode is a POD type, you could write
std::uninitialzed_fill_n(chunk.data(), chunk.num_elements(), octreenode {1, 72});
but a smart library implementation would end up calling fill_n anyways (because there's no gain). You can use uninitialized_fill_n if octreenode is not a POD type, but it is trivially destructible.
In fact, there's no reason to use new on the multi array in the first place either. You can just use the constructor initialization list to construct the multi_array member
Live On Coliru
#include <boost/multi_array.hpp>
#include <type_traits>
struct octreenode { int a; int b; };
class world {
public:
world(double x, double y, double z, int widtheast, int widthnorth, int height)
:
originx(x), originy(y), originz(z),
chunkseast(widtheast), chunksnorth(widthnorth), chunksup(height),
chunk(boost::extents[chunksnorth][chunkseast][chunksup])
{
octreenode v = { 1, 72 };
std::fill_n(chunk.data(), chunk.num_elements(), v);
}
private:
double originx, originy, originz;
int chunkseast, chunksnorth, chunksup;
typedef boost::multi_array<octreenode, 3> planetchunkarray; // a boost_multi for chunks
typedef planetchunkarray::index index;
planetchunkarray chunk;
};
int main() {
world w(1,2,3,4,5,6);
}
Related
I'm making a simple Snake game. When making a map, my definition of the map is as follows
int map[25][25] = { 0 };
for (int i = 0; i < 25; i++)//Set the boundary to - 2
{
map[0][i] = -2;
map[24][i] = -2;
}
for (int i = 1; i < 25; i++)//Set the boundary to - 2
{
map[i][0] = -2;
map[i][24] = -2;
}
Then I made a function to simulate the motion of the snake。(The first parameter is the class I created: snake,The second is its moving direction. The key is the third parameter, the map array I put in.)
void snake_move(Snake snake1, int direction, int map[][25])
Then I made a call to the function.(The third parameter is the two-dimensional array pointer I passed in)
snake_move(snake1, direction, map);
Then the following figure appears
I found that it was a two-dimensional array before the function call,which is as follows
Why does this happen and how to solve this problem? I look forward to your reply・v・
You cannot pass built-in arrays like this to functions. snake_move(), even though it appears to have an argument that looks like a 2D array, it actually takes a pointer to a 1D array. This:
void func(int map[][25]);
Is actually equivalent to:
void func(int (*map)[25]);
map is a pointer to an array of 25 int elements. When you call that function:
func(map);
The map array "decays" to a pointer that points to its first element.
This is an unfortunate consequence of C++'s compatibility with C.
To avoid issues like this, use std::array (for fixed-size, static allocation of elements), or std::vector (for dynamically allocated elements.)
To get a 2D array, you need to use an array of arrays or a vector of vectors. For an array, that means:
std::array<std::array<int, 25>, 25>
This means "an array containing 25 arrays of 25 int elements.
It's a good idea to make snake_move take a const reference to avoid an unnecessary copy of the whole array. So:
#include <array>
void snake_move(
Snake snake1, int direction,
const std::array<std::array<int, 25>, 25>& map);
// ...
std::array<std::array<int, 25>, 25> map{};
for (int i = 0; i < 25; i++) {
map[0][i] = -2;
map[24][i] = -2;
}
for (int i = 1; i < 25; i++) {
map[i][0] = -2;
map[i][24] = -2;
}
snake_move(snake1, direction, map);
If snake_move() needs to modify the passed array, then remove the const.
To reduce the need to write the type over and over again, you can use an alias (with the using keyword):
using MapType = std::array<std::array<int, 25>, 25>;
void snake_move(Snake snake1, int direction, const MapType& map);
// ...
MapType map{};
// ...
The {} in the map declaration will initialize all values to zero. You can also use:
MapType map = {};
which does the same.
You can actually keep the dimension without using std::array
void snake_move(Snake snake1, int direction, int (&map)[25][25]);
https://godbolt.org/z/EYz7hzjTj
Also note it's not a 1D array (i.e. map[0] is not -2), the debug window does recognize and shows it's a int[25]*, it probably just have some bug that fail to display it in the correct format.
Why does this happen
Because of type decay. In particular, in many contexts (including when appearing as a parameter to a function), an array decays to a pointer to its first element. For example:
The type int [6] decays to int*
The type int *[6] decays to int**.
The type double [10] decays to double*.
The type int [5][6] decays to int (*)[6].
Thus, in you example, the third parameter int map[][25] is actually a pointer to an array of size 25 with elements of type int, ie int (*)[25].
how to solve this problem?
You can use std::array, as shown below:
void snake_move(Snake snake1, int direction,
//----------------------------vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv------->std::array used
std::array<std::array<int, 25>,25> map)
{
}
std::array<std::array<int, 25>,25> map; //sta::array used
If the function snake_move() doesn't change the passed std::array, and to avoid unnecessary copying, you can take the std::array as a reference to const:
void snake_move(Snake snake1, int direction,
const std::array<std::array<int, 25>,25>& map)
//----------------------------^^^^^-----------------------------------^----->lvalue reference to non-const std::array<std::array<int, 25>,25>
{
}
I have to create an array of array named m2DArray. It has 2 rows and 5 cols i;e a size of [2][5].
The array can be float** or double** which is known to me only at runtime.
Since I do not know the type at compile time, I initialize it in my header files as
void **m2DArray;
Then I create a template function:
template <typename SampleType>
void MyClass::initiliaze2DArray(SampleType** m2DArrayTyped)
{
m2DArray = new SampleType* [2]; //2 rows
int32 sizeOfOneCol = 5 * sizeof(SampleType); // 5 cols
for (int32 row = 0; row < 2; row++)
{
m2DArray[row] = new SampleType [sizeOfOneColumn];
memset(m2DArray[row], 0, sizeOfOneCol);
}
}
Then at runtime I decide between float** or double** based on some logic in the class and accordingly try to initalize the 2d array.
if (somelogictellsfloat){
float **mArrayFloat;
initiliaze2DArray(mArrayFloat);
}
else {
double **mArrayDouble;
initiliaze2DArray(mArrayDouble);
}
However, when trying to initialize this 2d array, I am not able to convert the void** to either float** or double** in my template function.
I get the following error:
error: invalid conversion from ‘double**’ to ‘void**’ in line m2DArray = new SampleType* [2];
My Question:
How do I cast **m2DArray from void ** to float ** or double ** ? Or is there a better way to create this 2d array at runtime.
You have some misunderstandings here. Most important: void** is NOT a generic pointer. The only generic pointer is void*. So, your type void** can hold the address of a generic void*. This you need to understand.
So, regardless, how many dimensions your array would have, you could assign it to a void*. And later, it is your responsibility to cast it to your needed type. See:
// Create a 5 dimensional array
char***** dimension5 = static_cast<char*****>( malloc(200));
// Assign it to a generic pointer
void* generic = dimension5;
// Get the address of the generic pointer
void** addressOfGeneric = &generic;
// Now dereference the address of the generic pointer to get back the generic pointer
// and cast it to our original type
char***** dimension5Later = static_cast<char*****>(*addressOfGeneric);
// Do something with the array
dimension5Later[0][0][0][0][0] = 'H';
But, of course we would never do that. In C++ we should never use raw pointers for owned memory. We should try to avoid raw pointers at all and work with smart pointers. We must not use malloc and we should not even use new and instead some make_unique-function.
I wonder, if you need that at all. Because you should use std::vector instead.
Please see the following:
// The dimension of our 2d vector
constexpr size_t NumberOfRows = 2U;
constexpr size_t NumberOfColumns = 5U;
bool somelogictellsfloat{ true };
// Depending on what to create
if (somelogictellsfloat) {
// Create and initialize a 2d vector for floats
std::vector<std::vector<float>> mArrayFloat(NumberOfRows, std::vector<float>(NumberOfColumns, 0.0));
}
else {
// Create and initialize a 2d vector for doubles
std::vector<std::vector<double>> mArrayDouble(NumberOfRows, std::vector<double>(NumberOfColumns, 0.0));
}
But what you really want, is to use the abstract factory pattern. If you do not know, the please read about this.
By the way. I made for your original code a minimum reproducable example. This you should always do in questions on SO.
I fixed some bugs and made it compilable. But please do not use
#include <cstring>
#include < cstdlib >
#include <vector>
struct MyClass {
void** m2DArray; // Address of a generic pointer
void* md;
float** mArrayFloat;
double** mArrayDouble;
using int32 = int;
template <typename SampleType>
void initiliaze2DArray(SampleType** m2DArrayTyped)
{
m2DArrayTyped = new SampleType * [2]; //2 rows
int32 sizeOfOneCol = 5 * sizeof(SampleType); // 5 cols
for (int32 row = 0; row < 2; row++)
{
m2DArrayTyped[row] = new SampleType[5];
memset(m2DArrayTyped[row], 0, sizeOfOneCol);
}
md = m2DArrayTyped; // md is now a generic pointer
m2DArray = &md; // and m2DArray is the address of that generic pointer
}
// Test function
void test(bool doFloatAndNotDouble) {
if (doFloatAndNotDouble) {
initiliaze2DArray(mArrayFloat);
}
else {
initiliaze2DArray(mArrayDouble);
}
}
};
// Driver code
int main() {
MyClass mc{};
mc.test(true);
mc.test(false);
return 0;
}
In my platformer game which I'm writing in Visual C++, each level will initially be stored as a 2-dimensional array of ints. I decided it would make more sense to store this array in a class, so I created a class called Level. It looks like this:
class Level {
private:
int map[20][30];
public:
Level(int a[20][30]) {
map = a;
}
int getcell(int row, int column) {
return map[row][column];
}
};
As far as I can see - from looking up tutorials on class constructors, and passing 2-dimensional arrays as parameters, this should work, so I really don't understand why it doesn't.
On the line where I do map = a, I get an error: Error: expression must be a modifiable lvalue. I've looked this error up on stackoverflow, but I can't find any answers which relate to my problem.
So, how can I fix this error?
This doesn't really have anything to do with a constructor. You cannot assign arrays in C++. Whether in the constructor, or anywhere else.
There are two ways to work around it. The first way is the brute force way. Instead of
map = a;
write a loop to copy the contents of the array from the constructor's parameter into the class member array.
The second way is to stuff the array into an intermediate class:
class Level {
public:
struct level_map {
int map[20][30];
};
private:
level_map map;
public:
Level(const level_map &initial_map) : map(initial_map)
{
}
int getcell(int row, int column) {
return level_map.map[row][column];
}
};
This may or may not be practical, and introduces a little bit more complexity.
But the real answer here is to use std::vectors instead of plain arrays, which will solve all of these problems.
Others have already mentioned the real reason: you cannot assign an array to another using = operator. My two cents about your class:
map is not a good name, it may get conflict with std::map if using namespace std; or using std::map was specified somewhere.
The constant array sizes make this class non-reusable. Class should be flexible to allow any N*M sized 2D array. For this, better to use vector<vector<int>>.
getcell should be a const method, and it should do error checking with row and column numbers passed.
If you want this class to have static-sized array sizes and compile time, you may use class templates with row and column sizes as non type template arguments.
template<size_t row, size_t column>
class Level
{
int _map[row][column];
public:
Level(int src[row][column])
{
memcpy(_map, src, sizeof(_map)); // why not simply 'memcpy' ?
}
};
int main()
{
int source[10][2] = { {1, 2}, {3,4} };
Level<10, 2> ten_by_2(source);
}
Here the map is a constant value, which could not been assigned as an lvalue. This could be fixed by iterating the element of the array, and assign a[i][j] to map[i][j].
class Level {
private:
int map[20][30];
public:
Level(int a[20][30]) {
for(int i = 0; i < 20; ++i)
for(int j = 0; j < 30; ++j)
map[i][j] = a[i][j];
}
int getcell(int row, int column) {
return map[row][column];
}
};
I need a 2d array with fixed width and height that can only change the individual values stored in it. It is declared in a header and later initialized in a source file.
What I found made me try the following snippets; unfortunately questions were about either 1d or non-const arrays and did not match my situation.
int *const *const a = new int[10][10];
int *const *const b = new int[10][10]();
int *const *const c = new int*[10];
for (int i = 0; i < 10; ++i) {
c[i] = new int[10];
}
My hope was in the last example, but how can I use the "inner" arrays of c if they are not initialized and I am not able to initialize them since they are const?
Do I not need a different type for this array? I was thinking about int d[][] but it doesn't have constant width and height.
It seems to me like a paradox (if it exists in the c++ world), am I missing something?
I was thinking about int d[][] but it doesn't have constant width and height.
int d[][] does not make sense (and will be rejected by the compiler). As far as multi-dimensional arrays are concerned, only the first dimension's size can be omitted to denote an incomplete type. The other dimensions' sizes are part of the type. You cannot omit them, much like you cannot omit the int.
In other words, if you have something like int d[5][10], then you can think of it as a one-dimensional array of element type int[10]. Generally, think of multi-dimensional arrays as a special case of one-dimensional arrays. It will make everything easier to understand.
The best solution to your problem in C++ is to create a class with private std::array<T, Width * Height> data; and int width member variables inside, and calculate the array offset from individual x and y arguments in a public member function, for example:
T& operator()(int x, int y)
{
return data[y * width + x];
}
If the dimensions are only known at run-time, then the only thing you have to change is using std::vector instead of std::array. Storage will still be contiguous.
Here is a complete example:
#include <vector>
#include <iostream>
class Matrix
{
public:
Matrix(int width, int height, int value) :
width(width),
data(width * height, value)
{}
int& operator()(int x, int y)
{
return data[y * width + x];
}
private:
int width;
std::vector<int> data;
};
int main()
{
Matrix m(5, 10, 123);
std::cout << m(7, 8) << "\n";
m(7, 8) = 124;
std::cout << m(7, 8) << "\n";
}
My hope was in the last example, but how can I use the "inner" arrays of c if they are not initialized and I am not able to initialize them since they are const?
That's not really true at all:
int * const * const c = new int*[10]
{
new int[10], new int[10], new int[10], new int[10], new int[10],
new int[10], new int[10], new int[10], new int[10], new int[10]
};
So if I have a class with a 2D array that I want to initialize with two parameters passed into the constructor, how would I do that, I keep running into errors because it won't let me update the two-d array at all in the constructor.
-- Update from the comments:
In my header file I tried both
int array[][]
and
int **array
and then in the .cpp file in the constructor I'm trying to do
array = new int[arg1][arg2]
Neither declaration of the array in the header file worked.
in the constructor I'm trying to do array = new array[arg1][arg2]
You need to specify the array type, like
array = new int[arg1][arg2];
Note that this works in C++11 only - when using older standards, the second array size needs to be const (which is probably not what you want).
There are also some additional articles discussing the same issue:
Multi-Dimensional Arrays
How to "new" a two-dimension array in C++?
Ideally, since you are using C++ anyway, you should use std::vector as proposed in another answer.
Vectors use a lot of overhead though, don't they? I'm trying to keep my memory use light. –
Start with std::vector. Once your application is running properly from a functional perspective, if you are still concerned about memory usage and/or performance, do benchmarking. If you properly encapsulate your 2D array in a class, you can always change the actual implementation of the array with no impact on the code which uses it.
Technically, if you want to make sure that you have one flat memory area which contains your array, you could use a 1-dimensional array to simulate a 2-dimensional array, like in the following code (just to get you the idea, certainly needs some improvement, especially copy construction and assignment operators are missing):
class Array2D {
private:
int *array;
int size1;
public:
Array2D(int arg1, int arg2) {
size1 = arg1;
array = new int[arg1 * arg2];
}
~Array2D() {
delete[] array;
}
int& at(int i1, int i2) {
return array[i1 * size1 + i2];
}
};
int main() {
Array2D array(10, 10);
array.at(2, 2) = 42;
std::cerr << array.at(2, 2);
return 0;
}
Simplest solution would be:
std::vector<std::vector<VALUE>> arr2(X, std::vector<VALUE>(Y));
Here is an 2d array example with bounds check and custom type, based upon the example from Andreas Fester.
#include <stdexcept>
template <typename T>
class Array2D {
private:
T *array;
unsigned int sizeX;
unsigned int sizeY;
public:
Array2D(unsigned int X, unsigned int Y) {
sizeX = X;
sizeY = Y;
array = new T[X * Y];
}
~Array2D() {
delete[] array;
}
T& at(unsigned int X, unsigned int Y) {
if((X > sizeX) || (Y > sizeY))
throw std::out_of_range("Bla bla");
return array[X * sizeX + Y];
}
};
int main() {
double MyValue;
Array2D<double> *MyArray = new Array2D<double>(10, 100);
MyArray->at(1,1) = 10.1;
MyValue = MyArray->at(1,1);
printf("Array value = %3.3f\n", MyValue);
return 0;
}