need help creating a two dimensional vector that holds pointers of auser defined type

  • Thread starter dwightarmyofchampions
  • Start date
D

dwightarmyofchampions

I am having trouble with two-dimensional vectors. In my old code I had
a vector in my class definition that looked like this:

std::vector<ABC*> vec;

....which means I am declaring a vector whose elements will contain
pointers to ABC objects.


....and in my constructor I have:

// make sure vector is empty before populating it
vec.clear();

for (int i = 0; i < 5; i++)
{
ABC* abcobject1 = new ABC(i);
vec.push_back(abcobject1);
}

....and in my destructor I have:

for (std::vector<ABC*>::iterator it = vec.begin();
it != vec.end();
it++)
{
delete *it; *it = 0;
}

This is all well and good and appears to work OK. Now, let's suppose
that I want to make vec be a two-dimensional vector instead of a one-
dimensional vector. That is, the definition in my header file now
looks like this:

// vec is a vector of a vector of pointers to ABC objects
std::vector< std::vector<ABC*> > vec;

OK, now here's where I'm messing up. My constructor looks like this:

for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 5; j++)
{
ABC* abcobject1 = new ABC(j);
vec.push_back(abcobject1);
}
}

When the loop first iterates (i and j both equal zero) how can it
push_back an item onto vec[0] when vec[0] itself doesn't exist yet? I
think there needs to be a vec.push_back(...) line somewhere earlier,
but I don't know where and what should be pushed back. Can someone
help me?


Oh, and my destructor looks like this:

for (std::vector< std::vector<ABC*> >::iterator itOuter = vec.begin();
itOuter != vec.end();
itOuter++)
{
for (std::vector<ABC*>::iterator itInner = (*itOuter).begin();
itInner != (*itOuter).end();
itInner++)
{
delete *itInner; *itInner = 0;
}
}

I have no idea if that's correct, since I haven't gotten that far yet.
 
V

Victor Bazarov

I am having trouble with two-dimensional vectors. In my old code I had
a vector in my class definition that looked like this:

std::vector<ABC*> vec;

So, it's a non-static data member, right?
...which means I am declaring a vector whose elements will contain
pointers to ABC objects.


...and in my constructor I have:

// make sure vector is empty before populating it
vec.clear();

Why, FCOL? You're in the constructor. The vector has just been
constructed. How can it have anything?

I can understand having

assert(vec.empty());

here somewhere, just for kicks, but 'clear'?
for (int i = 0; i < 5; i++)
{
ABC* abcobject1 = new ABC(i);
vec.push_back(abcobject1);

And why can't you just do

vec.push_back(new ABC(i));

instead of defining a local to this loop variable? Just curious about
the motivation.
}

...and in my destructor I have:

for (std::vector<ABC*>::iterator it = vec.begin();
it != vec.end();
it++)
{
delete *it; *it = 0;

I can understand deleting (since you allocated it using 'new'), but why
do you care to set it to 0? The vector is going to be destroyed right
after the destructor's body finishes...
}

This is all well and good and appears to work OK. Now, let's suppose
that I want to make vec be a two-dimensional vector instead of a one-
dimensional vector. That is, the definition in my header file now
looks like this:

// vec is a vector of a vector of pointers to ABC objects
std::vector< std::vector<ABC*> > vec;

OK, now here's where I'm messing up. My constructor looks like this:

for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 5; j++)
{
ABC* abcobject1 = new ABC(j);
vec.push_back(abcobject1);


You can't index in a vector that doesn't have anything. 'vec' is empty
when you start, so evaluating 'vec' has undefined behaviour.
}
}

When the loop first iterates (i and j both equal zero) how can it
push_back an item onto vec[0] when vec[0] itself doesn't exist yet?

Right. It can't.
> I
think there needs to be a vec.push_back(...) line somewhere earlier,
but I don't know where and what should be pushed back. Can someone
help me?

What is the type of 'vec[0]'? What do you expect there? Come on, you
can do it...

It's a 'vector<ABC*>', of course! So, you need to push an instance of
that object onto your 'vec' before entering the inner loop:

for (int i = 0; i < 7; ++i)
{
vec.push_back(vector<ABC*>()); // now you have i-th element
for (int j = 0; j < 5; ++j)
vec.push_back(new ABC(j));
}
Oh, and my destructor looks like this:

for (std::vector< std::vector<ABC*> >::iterator itOuter = vec.begin();
itOuter != vec.end();
itOuter++)
{
for (std::vector<ABC*>::iterator itInner = (*itOuter).begin();
itInner != (*itOuter).end();
itInner++)
{
delete *itInner; *itInner = 0;
}
}

I have no idea if that's correct, since I haven't gotten that far yet.

Seems fine except for the unnecessary assigning of the 0.

V
 
D

dwightarmyofchampions

Thanks for the quick reply. Even though I've technically been
programming in C++ for eight years, I've always been really really bad
at it, so it's nice to be able to talk with people that really know
their stuff. You were correct about the assert and the vec.push_back
(new ABC(i));


So, it's a non-static data member, right?
Yes.




I can understand deleting (since you allocated it using 'new'), but why
do you care to set it to 0?  The vector is going to be destroyed right
after the destructor's body finishes...

I never really understood the rule about setting pointers to zero, it
was always something my professor told me to do "to be on the safe
side." So I just ended up setting all my pointers to zero after delete
statements without really giving much thought as to what it did.

So, when do need to set the opointer equal to zero and when is it
unnecessary?
It's a 'vector<ABC*>', of course!  So, you need to push an instance of
that object onto your 'vec' before entering the inner loop:

    for (int i = 0; i < 7; ++i)
    {
        vec.push_back(vector<ABC*>()); // now you have i-th element
        for (int j = 0; j < 5; ++j)
           vec.push_back(new ABC(j));
    }


OK, now here is something I am totally clueless about:

vector<ABC*>()

What is with the two parentheses at the end? Does that mean that it's
an empty vector? Is there anything we could put in those parentheses
if we wanted to?
 
B

Bart van Ingen Schenau

I never really understood the rule about setting pointers to zero, it
was always something my professor told me to do "to be on the safe
side." So I just ended up setting all my pointers to zero after delete
statements without really giving much thought as to what it did.

So, when do need to set the opointer equal to zero and when is it
unnecessary?

You only need to set a pointer to zero (or NULL) if the pointer will
continue to exist for some time, but you don't have a better value for
it.

Setting a pointer to zero in a destructor is always pointless, because
the pointer itself will shortly after cease to exist.
It's a 'vector<ABC*>', of course!  So, you need to push an instance of
that object onto your 'vec' before entering the inner loop:
    for (int i = 0; i < 7; ++i)
    {
        vec.push_back(vector<ABC*>()); // now you have i-th element
        for (int j = 0; j < 5; ++j)
           vec.push_back(new ABC(j));
    }


OK, now here is something I am totally clueless about:

vector<ABC*>()

What is with the two parentheses at the end? Does that mean that it's
an empty vector?


Technically it means that it is a (temporary) default-constructed
vector. A default-constructed vector is an empty vector, so the answer
to your question is Yes.
Is there anything we could put in those parentheses
if we wanted to?

Yes, you can specify the number f elements you want the vector to
initially have (and as second argument you can specify an element to
use as a basis to construct the initial elements from).

Bart v Ingen Schenau
 
D

dwightarmyofchampions

Thanks.

If anybody could send me some links to some sites explaining this
stuff a lot more thoroughly with examples it'd be GREATLY appreciated.
 
J

James Kanze

(e-mail address removed) wrote: [...]
...and in my destructor I have:
for (std::vector<ABC*>::iterator it = vec.begin();
it != vec.end();
it++)
{
delete *it; *it = 0;
I can understand deleting (since you allocated it using
'new'), but why do you care to set it to 0? The vector is
going to be destroyed right after the destructor's body
finishes...

Because technically, leaving a pointer to deleted memory in the
vector is undefined behavior. In fact, I think that even the
way he does it is undefined behavior (according to the
standard); you have to null the pointer in the vector *before*
the delete, with something like:

ABC* p = *it ;
*it = NULL ;
delete p ;

In practice, this is one case of undefined behavior I wouldn't
worry about. The standard may say undefined behavior, but his
original code, with or without the "*it = 0", is going to work,
everywhere.
 
J

James Kanze

On Apr 8, 5:46 am, (e-mail address removed) wrote:

[...]
I never really understood the rule about setting pointers to
zero, it was always something my professor told me to do "to
be on the safe side." So I just ended up setting all my
pointers to zero after delete statements without really giving
much thought as to what it did.

I'd suggest changing professors. There are only two cases where
setting the pointer to null makes sense: the first is if you're
sure that it's the only pointer to the object, and you're going
to check for null later. The second is when there is a risk
that the pointer may be copied or otherwise read later. The
second actually applies here (formally)---the problem is that
theoretically, at least, the old pointer value could be read in
the *it sub-expression of the assignment, before it has been set
to null, so this isn't sufficient.

In practice, the second reason is extremely theoretical to begin
with; there are very, very few machines where just reading an
invalid pointer value can cause problems, and then, only if it
has been invalidated at the OS level. (One of the very few
machines is the Intel 80x86, of course. But in fact, the more
usual OS's don't support segmented pointers, so the problem
doesn't exist, and even if the OS supported segmented pointers,
there are very good reasons for the compiler not to generate the
instructions which would cause problems.)
So, when do need to set the opointer equal to zero and when is
it unnecessary?

Basically, it's only necessary if you're going to check for
null, somewhere. And it can only work, really, if there are no
other pointers to the object.
It's a 'vector<ABC*>', of course! So, you need to push an instance of
that object onto your 'vec' before entering the inner loop:
for (int i = 0; i < 7; ++i)
{
vec.push_back(vector<ABC*>()); // now you have i-th element
for (int j = 0; j < 5; ++j)
vec.push_back(new ABC(j));
}

OK, now here is something I am totally clueless about:

What is with the two parentheses at the end?

Technically, it's what the standard calls an "explicit type
conversion (functional notation)". Practically, it creates a
using default said:
Does that mean that it's an empty vector?

Yes, since that's what vector's default constructor does.
Is there anything we could put in those parentheses if we
wanted to?

Anything that's acceptable as arguments to a constructor of
vector. Victor could also have written:

for ( int i = 0 ; i < 7 ; ++ i ) {
vec.push_back( vector< ABC* >( 5 ) ) ;
for ( int j = 0 ; j < 5 ; ++ j ) {
vec[ i ][ j ] = new ABC( j ) ;
}
}

For that matter, in the constructor initializer list, you could
have written:

: vec( 7, vector< ABC* >( 5 ) )

And then just used:

for ( int i = 0 ; i < 7 ; ++ i ) {
for ( int j = 0 ; j < 7 ; ++ j ) {
vec[ i ][ j ] = new ABC( j ) ;
}
}

Depending on the implementation, this might even be marginally
faster (although if you're new'ing each element, I can't imagine
that the difference would be measurable). But the push_back
idiom that Victor used is the consacrated idiom.
 
V

Victor Bazarov

James said:
(e-mail address removed) wrote: [...]
...and in my destructor I have:
for (std::vector<ABC*>::iterator it = vec.begin();
it != vec.end();
it++)
{
delete *it; *it = 0;
I can understand deleting (since you allocated it using
'new'), but why do you care to set it to 0? The vector is
going to be destroyed right after the destructor's body
finishes...

Because technically, leaving a pointer to deleted memory in the
vector is undefined behavior.

Could you substantiate this with anything, please?
> In fact, I think that even the
way he does it is undefined behavior (according to the
standard); you have to null the pointer in the vector *before*
the delete, with something like:

ABC* p = *it ;
*it = NULL ;
delete p ;

In practice, this is one case of undefined behavior I wouldn't
worry about. The standard may say undefined behavior, but his
original code, with or without the "*it = 0", is going to work,
everywhere.

V
 
J

James Kanze

If anybody could send me some links to some sites explaining
this stuff a lot more thoroughly with examples it'd be GREATLY
appreciated.

If you're just learning the STL, you need more than just a site.
You really need a good book. I'd recommend "The C++ Standard
Library: a Tutorial and Reference", by Nicolai Josuttis.
 
J

James Kanze

[...]
Even better, you should redeclare the whole object as:
std::vector< std::vector<ABC> >
and let the STL do all the allocation and deallocation for you.

If he can. It's not clear whether he's using pointers because
he's gotten bad habits from Java, or because the objects he's
pointing to aren't copiable. But in general: use (and copy)
objects when you can, use pointers (and dynamic allocation) when
you have to.
One thing to keep in mind is that std::vector is likely to do
quite a bit of internal memory copies, if your vector grows
and shrinks by a large amount. If your vector gets large
enough, you're going to take a performance hit. Generally,
vectors are designed for usage cases where you typically
allocate them once, right up front, and don't change their
size, until you no longer need the vector and you destroy it.

I've not found that to be the case. The standard requires
amortized constant time for push_back, and most implementations
seem to use a fairly large multiplier (between 1.5 and 2), which
means that reallocations and the subsequent copies are fairly
rare events. (On the other hand, you can waste a fair amount of
memory.) If he finds that initializing the vectors is a
performance issue, and he knows the final size, he can always
use reserve to ensure that there is no reallocation or copying
during the initialization. If the objects in the vector are
cheap to default initialize (the case with pointers and the
basic types), it's also worth considering initially creating the
vectors with the final size, and then using [] to insert the
actual initial values. (At least on the one implementation
where I measured it, resize() followed by [] was significantly
faster than reserve() followed by push_back(). Which surprised
me a bit, but that's what the measurements said.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,154
Members
46,702
Latest member
LukasConde

Latest Threads

Top