Skip to main content
gamedev.net
🔒 Locked

Passing an array of pointers to objects to a function

Started by ChaosWars Jun 29, 2008 at 11:41 AM 30 replies 8.9k views
Original Post
ChaosWars
ChaosWars
I have a rather odd problem. I am trying to pass an array of pointers to objects to a function, which initializes and fills the array. It then returns the number of pointers to objects in the array. The code looks something like this:

class Material(){..}

class MaterialLoader(){
...
LoadMaterial(const char *path, Material *materials[] ){...}
}
The wierd thing is, when the LoadMaterial function is called from another class like this:
Material **materials = NULL;
MaterialLoader ml;
int num_materials = ml.LoadMaterial(path, materials);
printf("adress of materials is %x\n", materials);
the printf points to 0! The array is getting filled within the function, debugging the application as well as printf statements inside LoadMaterial confirm this. It is as though a copy is being made of materials, and that this is being passed to the function, but that's not possible, is it? And yet it must be, since that seems to be what is happening. Can someone shed some light on this? If you want to compile the code, it can be obtained from http://code.google.com/p/mll/ Thanks in advance.
Linolium
Linolium
Your function

LoadMaterial(const char *path, Material *materials[] ){...}

is incorrect. With this implementation C++ is thinking the array materials[] already exists somewhere- which is incorrect.

It should be


int LoadMaterial(const char *path, Material ***materials)
{
int number_of_materials = CountMaterials(path); // <-- helper function

// Create the array (I think this syntax is correct)
*materials = new Matirial *[number_of_materials];

// Do normal loading
...

// Return the count as specified in the prototype
return number_of_materials;
}


-Linolium


**EDIT** Corrected pointer parameter. I knew something didn't seem right in that code segment. Thanks rip-off for the reminder ;)

Basically you would call the function like:
ml.LoadMaterial(path, &materials);
The Great and All Powerful Linolium
rip-off
rip-off
The pointer argument is a copy of the pointer passed to it. Changing the value of the "materials" argument in LoadMaterial() has no effect on the value of the variable "materials" in the other class.

One way would be to return a standard library container (vector) of (smart) material pointers:
#include <vector>class MaterialLoader{   std::vector<Material*> LoadMaterial(const std::string &path);};


Alternatively, you could pass the vector by reference.
#include <vector>class MaterialLoader{   void LoadMaterial(const std::string &path, std::vector<Material*> &materials);};


You could have returned the pointer in your code (but then you still have to do more manual memory management, and you have no size information):
class MaterialLoader{   Material **LoadMaterial(const std::string &path);};


The last solution (and IMO the worst choice) is to pass the pointer by reference. References to pointers are a little ugly syntax wise, but a typedef can help:
class MaterialLoader{   typedef  Material **ArrayOfMaterialPointers;   LoadMaterial(const std::string &path, ArrayOfMaterialPointers &materials);};


The last is really only shown for completeness and to demonstrate the references to pointers is a possible, if not always optimal, solution.

You code is very like this:
void foo( int *arg_ptr ){    delete arg_ptr;    arg_ptr = 0;}int main(){    int *main_ptr = new int(42);    foo(main_ptr);    // main_ptr was passed by value.    // the value of arg_ptr before it went out of scope is 0    // these changes are not made to main_ptr, it is now an invalid pointer}
ChaosWars
ChaosWars
Quote:
Original post by Linolium
Your function

LoadMaterial(const char *path, Material *materials[] ){...}

is incorrect. With this implementation C++ is thinking the array materials[] already exists somewhere- which is incorrect.

It should be


int LoadMaterial(const char *path, Material ***materials)
{
int number_of_materials = CountMaterials(path); // <-- helper function

// Create the array (I think this syntax is correct)
*materials = new Matirial *[number_of_materials];

// Do normal loading
...

// Return the count as specified in the prototype
return number_of_materials;
}


-Linolium


**EDIT** Corrected pointer parameter. I knew something didn't seem right in that code segment. Thanks rip-off for the reminder ;)

Basically you would call the function like:
ml.LoadMaterial(path, &materials);


I'm afraid this is incorrect. Actually, this was how I had it originally. It makes no difference to my problem. I was allocating the variable in the way you suggested by the way:

materials = new Material *[num_materials];
Linolium
Linolium
yes

int LoadMaterial(const char *path, Material **materials)
{
materials = new Material *[num_materials];
}

is incorrect. The tripple pointer is key here. Without it your allocated address will be lost.

-Linolium
The Great and All Powerful Linolium
ChaosWars
ChaosWars
Quote:
Original post by rip-off
The pointer argument is a copy of the pointer passed to it. Changing the value of the "materials" argument in LoadMaterial() has no effect on the value of the variable "materials" in the other class.

One way would be to return a standard library container (vector) of (smart) material pointers:
*** Source Snippet Removed ***

Alternatively, you could pass the vector by reference.
*** Source Snippet Removed ***

You could have returned the pointer in your code (but then you still have to do more manual memory management, and you have no size information):
*** Source Snippet Removed ***

The last solution (and IMO the worst choice) is to pass the pointer by reference. References to pointers are a little ugly syntax wise, but a typedef can help:
*** Source Snippet Removed ***

The last is really only shown for completeness and to demonstrate the references to pointers is a possible, if not always optimal, solution.

You code is very like this:
*** Source Snippet Removed ***


I was hoping to avoid using vector, since I want the data to be aligned, ready to pass to VBO's and the like. I would like to get the code working as it is, although if it isn't possible then so be it. It seems like the typedef solution may work, although I jsut tried it in this fashion (trying to understand how it works)

LoadMaterial(const char *path, Material &(**materials) )


and nmake complained that is was illegal

materialloader.h(42) : error C2528: 'materials' : pointer to reference is illegal
oler1s
oler1s
Quote:
The array is getting filled within the function,
Uh, what array? All I see is a Material**. So should I assume LoadMaterial allocates the array?

EDIT: Ahh, my post is so late, this is what I get for letting a post sit forever in a firefox tab.
ChaosWars
ChaosWars
Quote:
Original post by Linolium
yes

int LoadMaterial(const char *path, Material **materials)
{
materials = new Material *[num_materials];
}

is incorrect. The tripple pointer is key here. Without it your allocated address will be lost.

-Linolium


Yes, thank you very much. This is working - at least, it is comiling :p however, in order to check it, I need some more help :p

I had some other functions a bit later on, for example:

materials[cur_material]->SetAmbientColor( ambient_red, ambient_green, ambient_blue );

Now, the code:

*materials[cur_material] = new Material( name, const_cast< char* >( diffuse_map ) );
is compiling, but the code:

*materials[cur_material]->SetAmbientColor( ambient_red, ambient_green, ambient_blue );


fails with the error:

objmaterialloader.cpp(141) : error C2227: left of '->SetAmbientColor' must point to class/struct/union/generic type1>        type is 'mll::Material **'


how can I now access these functions?
chairthrower
chairthrower

precedence of the indirection

(*materials)[cur_material]->SetAmbientColor( ambient_red, ambient_green, ambient_blue );
Linolium
Linolium
the thing I would do normally is create stepper and accessor variables so member data can be accessed more cleanly w/o the risk of loosing the initial pointer. So adding on to my inital code:

int LoadMaterial(const char *path, Material ***materials)
{
// Get the total number of materials to add
int number_of_materials = CountMaterials(path);

// Create the array (I think this syntax is correct)
*materials = new Matirial *[number_of_materials];

// Do normal loading
Material **MaterialStepper = *materials;
for (int i=0; i {
MaterialStepper = new new Material(name, const_cast< char* >(diffuse_map));

// Temp accessor variable
Material *CurrentMaterial = MaterialStepper;

// Do you other stuff on the new object's data
CurrentMaterial->SetAmbientColor(ambient_red, ambient_green, ambient_blue);
}

// Return the count as specified in the prototype
return number_of_materials;
}


Or something similar.

-Linolium
The Great and All Powerful Linolium
fpsgamer
fpsgamer
First of all the declaration Material *materials[] is not of type pointer-to-array, it is a an array of pointers-to-maerials which decays to Material **material. Pointer-to-array looks like this: Material (*materials)[DIMENSION] (note the parenthesis)

Furthermore, you're not even passing an array to the function. You're passing a pointer that will later point to a heap allocated buffer.

You simply have some pointer confusion, what you want to do is this:

int LoadMaterial(const char *path, Material** materials){     ...     *materials = new Material[someNumber];     ....     return someNumber;}...Material* materials;count = LoadMaterial("some path", &materials)
ChaosWars
ChaosWars
The correct solution turned out to be:

int OBJMaterialLoader::LoadMaterials( const char &material_file, Material **&materials )


Allocation and accessing of the memory is then as per usual, ie:

materials = new Material *[num_materials];materials[cur_material] = new Material( name, const_cast< char* >( diffuse_map ) );materials[cur_material]->SetAmbientColor( ambient_red, ambient_green, ambient_blue );


Rather neat actually :)

Thanks for all the help from everyone though, you were very prompt and helpfull :) hopefully this thread will help others.

I have anotehr problem now though, namely that calling delete[] on a variable triggers an error report about HEAP CORRUPTION :p

CRT detected that the application wrote to memory after end of heap buffer


Somewhere, I think I must be writing past the end of an array, but I can't for the life of me see where :(
ChaosWars
ChaosWars
Quote:
Original post by fpsgamer
First of all the declaration Material *materials[] is not of type pointer-to-array, it is a an array of pointers-to-maerials which decays to Material **material.


I never said that it was I think. The thread title says: Passing an array of pointers to objects to a function
ChaosWars
ChaosWars
Quote:
Original post by ChaosWars
I have anotehr problem now though, namely that calling delete[] on a variable triggers an error report about HEAP CORRUPTION :p

CRT detected that the application wrote to memory after end of heap buffer


Somewhere, I think I must be writing past the end of an array, but I can't for the life of me see where :(


Oh God, what a stupid mistake :p

else if( buffer.substr( 0, 1 ) == "o" ){            name = new char[buffer.length() - 2];            sscanf(buffer.c_str(), "o %s", name);


I was allocating like that for certain variables. But I was not allowing space for the terminating \0 like that. It should have been buffer.length() - 1, obviously.
Enigma
Enigma
Quote:
Original post by ChaosWars
I was hoping to avoid using vector,
Don't.
Quote:
since I want the data to be aligned, ready to pass to VBO's and the like.
Which, conveniently, vectors are.

Use vector.

Σnigma
ChaosWars
ChaosWars
Quote:
Original post by Enigma
Quote:
Original post by ChaosWars
I was hoping to avoid using vector,
Don't.
Quote:
since I want the data to be aligned, ready to pass to VBO's and the like.
Which, conveniently, vectors are.

Use vector.

Σnigma


You can't pass a vector directly to a VBO without some manipulation.
fpsgamer
fpsgamer
Quote:
Original post by ChaosWars
You can't pass a vector directly to a VBO without some manipulation.


std::vector myVector;
int* ptr = &myVector[0];
Zakwayda
Zakwayda
Quote:
You can't pass a vector directly to a VBO without some manipulation.
That isn't a good reason not to use std::vector.

In any case, the 'manipulation' you speak of often amounts to no more than:
some_function(&v.front());
Simple enough, yes?
Trienco
Trienco
Quote:
Original post by ChaosWars
You can't pass a vector directly to a VBO without some manipulation.


Then derive your own vector class with a cast operator

template<typename T>class myVector : public std::vector<T>{  public:    operator T*() { return &front(); }};


Just don't spam implicit cast operators left and right or you might end up getting tons of "help, everything is ambigious"-errors. Also, trying something like
float* ptr = vec;
will fail miserably if the vector is empty. Also, expect all existing pointers to become invalid if you insert more elements (will typically only happen if it grows beyond the next power of two, but don't count on it).
f@dzhttp://festini.device-zero.de
ChaosWars
ChaosWars
Quote:
Original post by jyk
Quote:
You can't pass a vector directly to a VBO without some manipulation.
That isn't a good reason not to use std::vector.

In any case, the 'manipulation' you speak of often amounts to no more than:
some_function(&v.front());
Simple enough, yes?


It is if you are trying to develop something that doesn't make indiscriminate use of processor cycles. My aim with this is to develop something that is fast and mininimal, and while vectors would be the easy way out, they bring too much overhead IMO for the task at hand. Once the fields are read the arrays are static, and will never change. Therefor, using vectors would be trading off initial ease of use for memory bloat down the line. This is the reason that I don't want to use them.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.