Skip to main content
GameDev.net gamedev.net
🔒 Locked

issues converting from arrays to std::vector

Started by synth_cat Jul 18, 2006 at 6:49 PM 32 replies 4.2k views
Original Post
synth_cat
synth_cat
I have recently decided to try converting the arrays within my game to std::vector, due to pressure from forum members :) I have already been asking questions about this in an unrelated thread, but I thought I should put these questions under their own topic. Here's the situation: I have a few main objects in my game. The arrays I want to convert are members of these objects. These are the arrays that I want to convert: -CELL cells[], SHOT shots[], and PUFF puffs[] (these are all game logic arrays; a CELL is a moving entity, a SHOT is a projectile, and a PUFF is a simple particle. These three are all struct arrays, not class arrays.) -some arrays of vertices which I memcpy() to my LPDIRECT3D9VERTEXBUFFER at draw time. Here are my questions: 1) Is it possible and practical to turn my vertex arrays into std::vectors? What function do I use to copy memory from an std::vector to a direct3d vertex buffer? 2) Does anyone know what's going on with the problem described below? The following is a quote from another thread describing my attempt to convert a small SHOT shot_types array (used to define characteristics for types of projectiles) to a std::vector ////////////////////////////////////BEGIN QUOTE//////////////////// I have a question about std::fill(), which I want to be able to use in the same way I used ZeroMemory() to zero out arrays. Jpetrie, I tried out what you told me and there was a problem. Here's what I did:

//define a struct somewhere
struct SHOT
{
	//simple flags
	bool alive;
	ShotType type;

	//physical dimensions
	D3DXVECTOR2 pos;
	D3DXVECTOR2 mov;
	float height;
	float radius;
	float tail_length;
	float damage; //reflects impact of shot
	float life; //from 0 to 1
	float wall_loss; //from 0 to 1 - nonfactored by fps_factor
	float fade_time; //from 0 to 1 -represents the amount of life the
	//shot loses in one second
	D3DXVECTOR2 uv; //texcoords
};

//within class declaration
std::vector<SHOT> shot_types;

//within constructor
shot_types.resize(NUM_SHOT_TYPES);
std::fill(shot_types.begin(),shot_types.end(),NULL);




The resize() command works OK, but there is a problem with the std::fill() command. I do not understand the error message for this command. I have it right here: .............................................................................. c:\program files\microsoft visual studio 8\vc\include\xutility(2726) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const int' (or there is no acceptable conversion) game_structs.h(147): could be 'SHOT &SHOT::operator =(const SHOT &)' while trying to match the argument list '(SHOT, const int)' c:\program files\microsoft visual studio 8\vc\include\xutility(2754) : see reference to function template instantiation 'void std::_Fill,int>(_FwdIt,_FwdIt,const int &)' being compiled with [ _Ty=SHOT, _Alloc=std::allocator, _FwdIt=std::_Vector_iterator> ] synthgame.cpp(36) : see reference to function template instantiation 'void std::fill,int>(_FwdIt,_FwdIt,const int &)' being compiled with [ _Ty=SHOT, _Alloc=std::allocator, _FwdIt=std::_Vector_iterator> ] ..................................................................... The problem seems to be with the '0' parameter within the std::fill() call. The problem persisted when I changed it to NULL and to (SHOT)0 (which is undefined, anyway.) Can you please tell me why I am unable to do std::fill()? Does std::vector::resize() always set everything to NULL on it's own? Even if it does, I still need to be able to use std::fill() because some of my vectors will need to be "cleared" (not resized, but just set to all NULL values) at various points in the game. ////////////////////////////////////END QUOTE//////////////////// Thanks a lot! -synth_cat
Greg Philbrick, Game Developercoming soon . . . Overhauled CellZenith
deathkrush
deathkrush
You can't initialize an object to NULL (unless you provide a special constructor or operator=). You can, however set a pointer to null. If that's what you wanted, change std::vector to std::vector.
deathkrushPS3/Xbox360 Graphics Programmer, Mass Media.Completed Projects: Stuntman Ignition (PS3), Saints Row 2 (PS3), Darksiders(PS3, 360)
SiCrane
SiCrane
1) If you can do it with an array, you can do it with a vector. In particular you can get the address of the first element as a pointer with &*begin().

2) std::vector will fill all elements with T() when using reszie(). With a POD struct, it will zero initialize all elements of the structure.

Because your structure has no constructor that accepts NULL as an argument, you can't use NULL with std::fill(). You can, however, use std::fill(shot_types.begin(), shot_types.end(), SHOT()).

synth_cat
synth_cat
Quote:

You can't initialize an object to NULL

Well, that's not exactly what I want to be able to do. I just want to be able to make every member of an object be NULL, if there's a difference. If ZeroMemory() can NULL out an array of structs, why wouldn't std::vector provide something that does the same thing?

Any leads on the vertex array-as-std::vector problem?
Greg Philbrick, Game Developercoming soon . . . Overhauled CellZenith
Palidine
Palidine
you can't fill a vector of structs/classes with NULL. NULL is a value to which you can set pointers. if you want to fill the vector with NULL you want:

std::vector shot_types;

-me
Palidine
Palidine
Quote:
Original post by synth_cat
If ZeroMemory() can NULL out an array of structs, why wouldn't std::vector provide something that does the same thing?


because vector is a dynamic array. there's no need to fill it with NULL, you just don't fill it. you then only add what you need into the vector, dynamically, and you're done.

-me



deathkrush
deathkrush
Quote:
Original post by synth_cat
Quote:

You can't initialize an object to NULL

Well, that's not exactly what I want to be able to do. I just want to be able to make every member of an object be NULL, if there's a difference. If ZeroMemory() can NULL out an array of structs, why wouldn't std::vector provide something that does the same thing?

Any leads on the vertex array-as-std::vector problem?


In that case, do

SHOT nullShot;
memset(&nullShot, 0, sizeof(SHOT));
std::fill(shot_types.begin(), shot_types.end(), nullShot);
deathkrushPS3/Xbox360 Graphics Programmer, Mass Media.Completed Projects: Stuntman Ignition (PS3), Saints Row 2 (PS3), Darksiders(PS3, 360)
SiCrane
SiCrane
You know, I don't post just because I like the sound of my typing.
Quote:
Original post by SiCrane
You can, however, use std::fill(shot_types.begin(), shot_types.end(), SHOT()).


synth_cat
synth_cat
Quote:

You can, however, use std::fill(shot_types.begin(), shot_types.end(), SHOT()).


Sorry - I was about to ask about that but I had to break for a sec to track a redundant SetRenderState() call...

Anyway, it looks as if SHOT() is some sort of struct constructor that can be written to set everything to zero. This looks like what I'm looking for - could you please show me an example of what it looks like in the struct SHOT definition?

Quote:

n that case, do

SHOT nullShot;
memset(&nullShot, 0, sizeof(SHOT));
std::fill(shot_types.begin(), shot_types.end(), nullShot);

Would this be fast, out of curiosity? Is it safe std::vector usage?

Quote:

because vector is a dynamic array. there's no need to fill it with NULL, you just don't fill it. you then only add what you need into the vector, dynamically, and you're done.

I suppose that's true in almost all cases. But what if I want my vector to remain a certain size and still be able to NULL it out? For example, if I were to use std::vectors to store VERTEX info, I would not want the vectors to ever change size; I would just do one resize() call in construction and never use push_back() or clear(). However, I would still want to be able to NULL out the vertex vectors at the beginning of each step.

Thanks for all the responses!

-synth_cat
Greg Philbrick, Game Developercoming soon . . . Overhauled CellZenith
deathkrush
deathkrush
Quote:
Original post by synth_cat
Would this be fast, out of curiosity? Is it safe std::vector usage?


Well, in a Debug build it's probably not as fast as

memset(&shot_types[0], 0, shot_types.size()*sizeof(SHOT))

but at least std::fill guarantees that it won't mess up your std::vector. In a Release build there is probably no difference in speed between memset and std::fill. You can always write a benchmark and test it, if you are that desparate :-)
deathkrushPS3/Xbox360 Graphics Programmer, Mass Media.Completed Projects: Stuntman Ignition (PS3), Saints Row 2 (PS3), Darksiders(PS3, 360)
Zahlman
Zahlman
Quote:
Original post by synth_cat
Quote:

You can, however, use std::fill(shot_types.begin(), shot_types.end(), SHOT()).


Sorry - I was about to ask about that but I had to break for a sec to track a redundant SetRenderState() call...

Anyway, it looks as if SHOT() is some sort of struct constructor that can be written to set everything to zero. This looks like what I'm looking for - could you please show me an example of what it looks like in the struct SHOT definition?


Assuming D3DXVECTOR2 is the ordinary DX thingie, and ShotType in turn has a zero-arg constructor that "sets everything to zero", it looks like this:

Quote:



:)

Remember, in C++, classes and structs are equivalent. A class/struct with no defined constructors will by default have a zero-arg constructor defined for it. This constructor zeros out primitive members (unlike what happens with free variables of primitive type, which are not initialized implicitly) and calls zero-arg constructors (recursively) for all members of class/struct type.

It will also have a 'copy constructor' generated in similar fashion: this is the constructor used for initializing an object based off another one of the same type, as in "SHOT x; SHOT y(x);" <-- y is copy-constructed from x. Note that "SHOT y = x;" also invokes the copy constructor, *not* assignment. It is fundamentally different from "SHOT y; y = x;". Anyway, the default copy constructor does as you'd expect: primitive members are copied bitwise, and class/struct members get copy-constructed recursively.

Please keep in mind that for *some* objects, the "zeroed out" state IS NOT all zero bytes across the layout of the object, which is why ZeroMemory and its ilk are extremely bad mojo when dealing with objects. Honestly, forget about ZeroMemory. std::copy and std::fill do all the same stuff for primitives, respect objects and never make you worry about the sizeof() the things you're working with. Plus the compiler knows special tricks that will let it "do all the same stuff for primitives" *in the same way*, thus no performance loss. Nothing to lose and lots to win.

If you define any constructors yourself, then the default no-arg constructor will be suppressed (not sure about that copy constructor). This allows you to create structures that can't be default-constructed. This can be a good thing (helps verify that you don't have bogus objects in your system), but it also prevents you from doing a lot of useful things (like store them in containers). Therefore, you often add this constructor back in if you have another one.

A constructor should normally do as much work as possible with the "initializer list":

// BADclass Foo {  int i;  public:  Foo(int i) {    this->i = i;  }};// GOODclass Foo {  int i;  public:  Foo(int i) : i(i) {}}


The initializer list:
- Is standard, idiomatic usage of the language.
- Allows you to have the parameter name match the member name, due to the scoping rules.
- Is the *only* way you can initialize (a) const members; (b) members which don't have a no-arg constructor.
- Is more efficient: it specifies *how to initialize* the member, which *always will be initialized regardless* (even primitives, as mentioned above). Therefore you don't waste time invoking an ignored default-initialization and then re-assigning over that.

You can do a surprising amount with the initializer list, because the parentheses accept an *expression* in terms of the parameters plus anything else that might be in scope.

Quote:

I suppose that's true in almost all cases. But what if I want my vector to remain a certain size and still be able to NULL it out? For example, if I were to use std::vectors to store VERTEX info, I would not want the vectors to ever change size; I would just do one resize() call in construction and never use push_back() or clear(). However, I would still want to be able to NULL out the vertex vectors at the beginning of each step.


Why do you care if the vector changes size? It knows and can tell you what the current size is. What you *want* it to do is hold everything you put into it. Having a set size in mind ahead of time is counter to that goal.
synth_cat
synth_cat
Well, thanks for all the replies! I'm not sure I yet have a handle on this problem, though.

First of all, I want to address something you said, Zahlman:
Quote:

A class/struct with no defined constructors will by default have a zero-arg constructor defined for it.

I know for a fact that this is not always true - I remember that my d3d class object would by default set some pointers to 0xcccccccc and I had to add my own constructor just to get them zeroed.

http://www.gamedev.net/community/forums/topic.asp?topic_id=399377


Quote:

Assuming . . . ShotType in turn has a zero-arg constructor that "sets everything to zero . . . "

How does an enumerated type have a constructor? What does that look like in code? (Or is there a default zero constructor provided for enumerated types?)

Quote:

Why do you care if the vector changes size? It knows and can tell you what the current size is. What you *want* it to do is hold everything you put into it. Having a set size in mind ahead of time is counter to that goal.

The reason I want the std::vector to stay the same size is that the whole thing will be dumped to a vertex buffer, and I cannot afford to let the vector get bigger than the buffer.

Quote:

but at least std::fill guarantees that it won't mess up your std::vector.

Out of curiosity (which I have a lot of), how could memset() mess up a vector? By writing out of bounds?

I can tell by the responses I'm getting that I really don't understand the way std::vector is supposed to work. So let's say I did convert my proxy vertex array (VERTEX verts[VERTEX_BUFFER_SIZE]) to std::vector verts. My first impulse would be to resize() verts to VERTEX_BUFFER_SIZE in construction of my d3d object, and never change its size afterwards. At the beginning of each step I would zero out the vector using std::fill(). Then I would call some function like AddQuadToProxBuffer() which would track external number_of_vertex variables and write vertex info to verts, continually keeping track of the limit defined by VERTEX_BUFFER_SIZE (which is an enumerated value, not a #define, if that makes you guys happy :) .) Push_back() would never be called. At the end of a step I would call std::copy() to dump the info from verts to the LPDIRECT3D9VERTEXBUFFER.

Now I'm sure the approach outlined above is not kosher, because it doesn't really make use of any of the methods of an std::vector, which must be there for a good reason. What would be the better way to do this? I am also curious about the way I should use std::vector to store entities which stay alive for more than one step, like PUFFS, SHOTS, and CELLS, but I'm sure I've already asked enough questions in one post so I'll query that one later.

Thanks very much for all the help!

-synth_cat
Greg Philbrick, Game Developercoming soon . . . Overhauled CellZenith
SiCrane
SiCrane
Not all structs or classes have a default zero intialize constructor. It's only POD classes that are initialized with () will be zero initialized. This actually applies to all basic types like integers, etc.

int i = int(); // i = 0
int i; // i = who knows what

Similarly since your SHOT structure contains only primitives and POD types and no member functions it qualifies as a POD type. So when you initialize SHOT with () then it will be zeroed.

SHOT s = SHOT(); // all zeros
SHOT s; // who knows what
Zahlman
Zahlman
Er, yes. C++ is really weird about that eh :( I always get this stuff messed up.

Anyway, you can deal with the enumerated type easily enough:

struct SHOT {  // other stuff  public:  SHOT() : type(DEFAULT_SHOT_TYPE) {}};


Quote:

Quote:

Why do you care if the vector changes size? It knows and can tell you what the current size is. What you *want* it to do is hold everything you put into it. Having a set size in mind ahead of time is counter to that goal.

The reason I want the std::vector to stay the same size is that the whole thing will be dumped to a vertex buffer, and I cannot afford to let the vector get bigger than the buffer.


1) Who's dictating the size of the vertex buffer?
2) Why can't you just check the current size of the vector, and copy max(vector size, buffer size) to the buffer?
3) If you *do* get "more vertices than you can handle", how are you expecting to handle them, anyway?

Quote:

Out of curiosity (which I have a lot of), how could memset() mess up a vector? By writing out of bounds?


By writing zero values to bytes that are not supposed to be zero in an "empty" vector. It's up to the implementation how things are represented.

Quote:
My first impulse would be to resize() verts to VERTEX_BUFFER_SIZE in construction of my d3d object, and never change its size afterwards.


But as we're trying to explain to you, this defeats the purpose.

Quote:
At the beginning of each step I would zero out the vector using std::fill().


Why? Do you currently zero out the array with memset every time?

Quote:
Then I would call some function like AddQuadToProxBuffer() which would track external number_of_vertex variables and write vertex info to verts, continually keeping track of the limit defined by VERTEX_BUFFER_SIZE (which is an enumerated value, not a #define, if that makes you guys happy :) .)


A const int is probably better (for reasons of clarity). :) You wouldn't have to track a variable yourself, but instead call the vector's .size() every time you need that value (or perhaps cache it in a local variable of some function that uses the value).

Quote:
Now I'm sure the approach outlined above is not kosher, because it doesn't really make use of any of the methods of an std::vector, which must be there for a good reason. What would be the better way to do this? I am also curious about the way I should use std::vector to store entities which stay alive for more than one step, like PUFFS, SHOTS, and CELLS, but I'm sure I've already asked enough questions in one post so I'll query that one later.


You need to show some code. Please. Some things just can't be discussed this abstractly.
jpetrie
jpetrie
Quote:

I know for a fact that this is not always true - I remember that my d3d class object would by default set some pointers to 0xcccccccc and I had to add my own constructor just to get them zeroed.


You are incorrect(*). 0xCCCCCCCC is being written for you by the compiler in debug mode, it is not standard and will not happen in release mode (see this).

(*) Rather, your result is obviously correct, but the conclusion you drew as to why it happens is not.

Quote:

- Is the *only* way you can initialize (a) const members; (b) members which don't have a no-arg constructor.

And references.
synth_cat
synth_cat


Quote:

1) Who's dictating the size of the vertex buffer?
2) Why can't you just check the current size of the vector, and copy max(vector size, buffer size) to the buffer?
3) If you *do* get "more vertices than you can handle", how are you expecting to handle them, anyway?

1) Me :) This is a really low-level structure thing; I define how big my vertex buffers can be - I don't let any code mess with stuff like that.
2)Good point - I'm working on rethinking that.
3)The game is structured so that if you make a call to add vertices when the proxy buffer is already full, nothing happens. This may seem crude, but it actually saves me a lot of trouble.

Quote:

You are incorrect. 0xCCCCCCCC is being written for you by the compiler in debug mode, it is not standard and will not happen in release mode (see this).

Rather, your result is obviously correct, but the conclusion you drew as to why it happens is not.

Thanks for explaining that! However, the information doesn't help me much, since I need my program to work right in Debug just as much as in Retail.

............................................................................

OK, with your help I think I can take a stab at this . . .

Here's what I want to try: I declare std::vector in my class definition and don't do anything to it in construction. Then I change my AddQuad() function to just push_back() the vector to add a new vertex to it, checking std::vector::size() to make sure there aren't already vertex_buffer_size number of vertices in the vector. At the beginning of each step I do std::vector::clear() to reset the size of the vector to zero.

So would that approach be practical? Is it OK to clear() vectors on every step, or is it slow or unsafe?

Thanks!

-synth_cat
Greg Philbrick, Game Developercoming soon . . . Overhauled CellZenith
Zahlman
Zahlman
Quote:
Original post by synth_cat


Quote:

1) Who's dictating the size of the vertex buffer?
2) Why can't you just check the current size of the vector, and copy max(vector size, buffer size) to the buffer?
3) If you *do* get "more vertices than you can handle", how are you expecting to handle them, anyway?

1) Me :) This is a really low-level structure thing; I define how big my vertex buffers can be - I don't let any code mess with stuff like that.


Who are you passing the vertex buffer to - some kind of DX function? Does *it* expect a specific size of buffer? I'm guessing it expects a pointer to an array, plus an element count.

Don't limit the vector, then. Instead, check the current size, and tell DX about that size, instead of feeding it the constant size value every time. You don't need to copy out into an array, either. The vector storage is guaranteed to be contiguous, so you can get a pointer to the first element and use it like an array pointer. In fact, you probably won't need to copy it at all:

d3dxFunction(VERTEX* vertices, int count);vector<VERTEX> myVertices;// put stuff into the vectord3dxFunction(&(myVertices[0]), myVertices.size());


Quote:

So would that approach be practical? Is it OK to clear() vectors on every step, or is it slow or unsafe?


Perfectly safe, and actually reasonably fast *itself*. clear() doesn't actually deallocate the memory or scrub over it; it just says "OK, now zero of the elements are real elements". However, the clearing does mean that you'll need to re-add all the vertices that didn't change. Is it really the case that you need to calculate your vertices from scratch every time? There is probably something wrong with your algorithms...

Conceptually, a vector looks really vaguely like this:

template <typename T>class sample_vector_implementation_never_do_this_yourself {  size_t _size; // the number of things actually in use  size_t _capacity; // the number of elements representable by the allocated memory  T* data;  public:  // lots of other functions, including other constructors  sample_vector_implementation_never_do_this_yourself(int initial_size = 0,     T x = T()): _size(initial_size), _capacity(initial_size),     data(new T[initial_size]) {    std::fill(data, data + _capacity, x);  }  sample_vector_implementation_never_do_this_yourself(const     sample_vector_implementation_never_do_this_yourself& other) :     _size(other._size), _capacity(other._capacity), data(new T[other._capacity]) {    std::copy(data, data + _capacity, other.data);  }  sample_vector_implementation_never_do_this_yourself& operator=(const     sample_vector_implementation_never_do_this_yourself& rhs) {    sample_vector_implementation_never_do_this_yourself other(rhs);    swap(rhs);    return *this;  }  void swap(const sample_vector_implementation_never_do_this_yourself& rhs) {    std::swap(data, other.data);    std::swap(_size, other._size);    std::swap(_capacity, other._capacity);  }  ~sample_vector_implementation_never_do_this_yourself() { delete[] data; }  void clear() { resize(0); }  int size() { return _size; }  void resize(int new_size) { _size = new_size; }  void push_back(const T& value) {    if (_size == _capacity) {      // we need to reallocate because we ran out of room      // The implementation will always increase the size exponentially      // (i.e. multiplying by some factor each time rather than just adding),      // but the factor is up to the implementation.      sample_vector_implementation_never_do_this_yourself<T> tmp(_capacity * 2);      std::copy(data, data + _size, tmp.data);      tmp._size = _size;      // Now that vector represents the reallocated version of ourselves, we can      // swap it back in.      swap(tmp);    }    // Now we know we have enough capacity.    data[_size++] = value;  }};


The real implementation does lots of other smart things that are beyond the scope of this post. In particular, it will make use of 'placement new' and explicit destructor calls in order to avoid initializing the "unused portion" of the 'capacity'.
jpetrie
jpetrie
Quote:

Thanks for explaining that! However, the information doesn't help me much, since I need my program to work right in Debug just as much as in Retail.


Of course, and having a constructor that initializes members to known safe values (such as zero) as you have done is correct. The debug fills like 0xCCCCCCCC and its kin are there to help you track down (while debugging) situations where you've neglected to properly initialize something.
synth_cat
synth_cat
Quote:

Who are you passing the vertex buffer to - some kind of DX function? Does *it* expect a specific size of buffer? I'm guessing it expects a pointer to an array, plus an element count.

Don't limit the vector, then. Instead, check the current size, and tell DX about that size, instead of feeding it the constant size value every time. You don't need to copy out into an array, either. The vector storage is guaranteed to be contiguous, so you can get a pointer to the first element and use it like an array pointer. In fact, you probably won't need to copy it at all:

I'm afraid you misunderstood me - all I want to do is a DrawPrimitive() call on a dynamic vertex buffer which contains different data every step. The only reason I want to use a proxy std::vector buffer is that I want to be able to call functions like AddQuad() that don't have to unlock the actual IDirect3dVertexBuffer9. Instead they will just push_back() the vector, and just before DrawPrimitive() I can std::copy the data from the vector to the vertex buffer.

Quote:

However, the clearing does mean that you'll need to re-add all the vertices that didn't change.

Is that slow? I would need to push_back() up to 250 (rough est.) verts each step.

Thanks!

-synth_cat

Greg Philbrick, Game Developercoming soon . . . Overhauled CellZenith
Zahlman
Zahlman
Quote:
Original post by synth_cat
Quote:

Who are you passing the vertex buffer to - some kind of DX function? Does *it* expect a specific size of buffer? I'm guessing it expects a pointer to an array, plus an element count.

Don't limit the vector, then. Instead, check the current size, and tell DX about that size, instead of feeding it the constant size value every time. You don't need to copy out into an array, either. The vector storage is guaranteed to be contiguous, so you can get a pointer to the first element and use it like an array pointer. In fact, you probably won't need to copy it at all:

I'm afraid you misunderstood me - all I want to do is a DrawPrimitive() call on a dynamic vertex buffer which contains different data every step. The only reason I want to use a proxy std::vector buffer is that I want to be able to call functions like AddQuad() that don't have to unlock the actual IDirect3dVertexBuffer9. Instead they will just push_back() the vector, and just before DrawPrimitive() I can std::copy the data from the vector to the vertex buffer.


I still don't see why you think you need to make a copy.

Quote:

However, the clearing does mean that you'll need to re-add all the vertices that didn't change.

Is that slow? I would need to push_back() up to 250 (rough est.) verts each step.[/quote]

It's ugly and probably not necessary. Slow? Slow is relative. If it runs fast enough, don't worry about that. Always profile first. But I would fix it anyway on general principle ;s

Topic Locked

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

Sign in to reply to this topic.