Template And Arrays

M

Marcelo De Brito

Hi!

I have created a template in which there is a generic vector/array
that can hold any type defined by the programmer or built-in types.
When I define the vector/array using built-in types (int, double,
etc.), I must insert data of the respective type in it. But when I
define the array using a programmer defined type (e.g., a class), I
can access the class' members even though I did not insert any object
in it! See the lines of code below for a better explanation.

class B { //Programmer defined type.
public:
B() {};
B(int idd) : id(idd) {};
B(const B&) {};
virtual ~B() {};
void setid(int idd) {id = idd;}
void dummy() {cout << "B::dummy()" << " " << "id = " << id <<
endl;}
private:
int id;
};

template <class T, int size = 10>
class D1 {
public:
D1() : index(0) {};
D1(const D1&) {};
virtual ~D1() {};
T vt[size];
void push(T* i) {
vt[index] = *i;
index++;
}
void exvet() {
for(int i = 0; i<index; i++)
cout << "vt[" << i << "] = " << vt << endl;
}
void vetsize() {cout << size << endl;}
private:
int index;

};

int main()
{
D1<int, 5> od4; //First with int.

for(int i = 0; i<5; i++)
od4.push(&i); //<- Inserting ints in the array. Fine.

od4.exvet(); //<- Shows the ints int the array, Fine too.

D1<B, 10> od5; //Now with the programmer defined type.

for(int i = 0; i<10; i++)
od5.vt.dummy(); //<- !!!!!!!HERE!!!!!! It compiles and run!
}

In my mind, when I declared "D1<B, 10> od5", I allocated memory for 10
elements of the type "B" -- as I did when I defined for the int type
"D1<int, 5> od4". BUT, in the "int" case, I had to insert data in the
vector "vt" of the template in order to properly access it. Therefore,
how could I access the members of "od5" if I did not insert any class
"B" object in the respective array "vt"?

Wouldn't a more suited way for this case be:

D1<B, 10> od5;

for(int i = 0; i<10; i++)
od5.push(new B); //Inserting class B objects.
for(int i = 0; i<10; i++)
od5.vt.dummy(); //Accessing members of the objects inserted.

How about it??

I appreciate any comment, suggestion, etc.

Thank You!

Marcelo
 
A

Alf P. Steinbach

* Marcelo De Brito:
I have created a template in which there is a generic vector/array

It's not a "generic vector/array", it is a fixed size raw array.

that can hold any type defined by the programmer or built-in types.

Well, this is subtle, but it can't hold just any type. If the type in question
requires construction but does not support default construction, then there's no
way to instantiate the array. std::vector solves this by using, well, advanced
techniques, but std::vector still doesn't support non-copyable element type.

When I define the vector/array using built-in types (int, double,
etc.), I must insert data of the respective type in it.

OK, taken as description of your class' interface.

But when I
define the array using a programmer defined type (e.g., a class), I
can access the class' members even though I did not insert any object
in it!

That's your choice as designer of the class.

It's a bad choice.

Don't expose implementation details.

See the lines of code below for a better explanation.

class B { //Programmer defined type.
public:
B() {};

This leaves the 'id' member with indeterminate value => later UB.

B(int idd) : id(idd) {};
OK.


B(const B&) {};

This leaves the 'id' member with indeterminate value => later UB.

virtual ~B() {};
void setid(int idd) {id = idd;}
void dummy() {cout << "B::dummy()" << " " << "id = " << id <<
endl;}
private:
int id;
};

template <class T, int size = 10>
class D1 {
public:
D1() : index(0) {};
OK.


D1(const D1&) {};

This leaves the 'index' member with indeterminate value => later UB.

Depending on the type T it also leaves the 'vt' member with indeterminate values
(which is OK), or default constructed values (which impacts negatively on
efficiency).

virtual ~D1() {};
OK.


T vt[size];

This is a raw array of constant size.

void push(T* i) {
vt[index] = *i;
index++;
}

This is bad design. It requires client code to use the address operator, and it
opens the door for nullpointer argument, cuasing UB (most likely a crash). Make
the formal argument type 'T const&'.


void exvet() {
for(int i = 0; i<index; i++)
cout << "vt[" << i << "] = " << vt << endl;
}


OK, debugging?

void vetsize() {cout << size << endl;}

OK, more debugging?

private:
int index;

};

int main()
{
D1<int, 5> od4; //First with int.

for(int i = 0; i<5; i++)
od4.push(&i); //<- Inserting ints in the array. Fine.

od4.exvet(); //<- Shows the ints int the array, Fine too.

D1<B, 10> od5; //Now with the programmer defined type.

for(int i = 0; i<10; i++)
od5.vt.dummy(); //<- !!!!!!!HERE!!!!!! It compiles and run!
So?


}

In my mind, when I declared "D1<B, 10> od5", I allocated memory for 10
elements of the type "B" -- as I did when I defined for the int type
"D1<int, 5> od4". BUT, in the "int" case, I had to insert data in the
vector "vt" of the template in order to properly access it.


No, you didn't.

Therefore,
how could I access the members of "od5" if I did not insert any class
"B" object in the respective array "vt"?

You can access the elements because you haven't prevented access.

That's your choice as class designer.

Make the array 'private' (or 'protected') to prevent client code access.


Wouldn't a more suited way for this case be:

D1<B, 10> od5;

for(int i = 0; i<10; i++)
od5.push(new B); //Inserting class B objects.
for(int i = 0; i<10; i++)
od5.vt.dummy(); //Accessing members of the objects inserted.

How about it??


Well, how about it?

Have you tried actually writing that (and compiling and running)?


Cheers & hth.,

- Alf
 
M

Marcelo De Brito

Hi, Alf!

Thank you very much for your comments and explanations!

Concerning all the UB stuffs you aforementioned, it was just my
lazyness of not taking them out when copying and pasting the code
here. :)

By the way, I appreciate your comments. Very useful, indeed.

What I have not understood so far is the possibility of accessing
class' members through an array that was just allocated, but has no
objects inserted in it.

However, I think the line "D1<B, 10> od5" automatically does all the
allocation and object creation work. It will allocate 10 positions in
the array "vt" and will declare/define them as the type "B", since
that is the way it is defined in the template, and, as the default
constructor "B()" needs no arguments, it is possible to the array "vt"
to be created holding 10 class "B" objects. The line inside the
template:

T vt[size];

Does all the necessary work and it is not needed to insert any objects
in it, since they have already been inserted/created. I think.

Have I got it right?

Thank You!

Marcelo
 
A

Alf P. Steinbach

* Marcelo De Brito:
Hi, Alf!

Thank you very much for your comments and explanations!

Concerning all the UB stuffs you aforementioned, it was just my
lazyness of not taking them out when copying and pasting the code
here. :)

By the way, I appreciate your comments. Very useful, indeed.

What I have not understood so far is the possibility of accessing
class' members through an array that was just allocated, but has no
objects inserted in it.

However, I think the line "D1<B, 10> od5" automatically does all the
allocation and object creation work. It will allocate 10 positions in
the array "vt" and will declare/define them as the type "B", since
that is the way it is defined in the template, and, as the default
constructor "B()" needs no arguments, it is possible to the array "vt"
to be created holding 10 class "B" objects. The line inside the
template:

T vt[size];

Does all the necessary work and it is not needed to insert any objects
in it, since they have already been inserted/created. I think.

Have I got it right?

Yep.


Cheers & hth.,

- Alf
 
B

Bart van Ingen Schenau

Marcelo said:
Hi, Alf!

Thank you very much for your comments and explanations!

Concerning all the UB stuffs you aforementioned, it was just my
lazyness of not taking them out when copying and pasting the code
here. :)

By the way, I appreciate your comments. Very useful, indeed.

What I have not understood so far is the possibility of accessing
class' members through an array that was just allocated, but has no
objects inserted in it.

However, I think the line "D1<B, 10> od5" automatically does all the
allocation and object creation work. It will allocate 10 positions in
the array "vt" and will declare/define them as the type "B", since
that is the way it is defined in the template, and, as the default
constructor "B()" needs no arguments, it is possible to the array "vt"
to be created holding 10 class "B" objects. The line inside the
template:

T vt[size];

Does all the necessary work and it is not needed to insert any objects
in it, since they have already been inserted/created. I think.

Have I got it right?

Yes.
And to see that the array always initially contains default-constructed
objects, try commenting out the B::B() constructor. Then the code will
no longer compile.
Thank You!

Marcelo

Bart v Ingen Schenau
 
J

James Kanze

I have created a template in which there is a generic
vector/array that can hold any type defined by the programmer
or built-in types. When I define the vector/array using
built-in types (int, double, etc.), I must insert data of the
respective type in it. But when I define the array using a
programmer defined type (e.g., a class), I can access the
class' members even though I did not insert any object in it!
See the lines of code below for a better explanation.
class B { //Programmer defined type.
public:
B() {};
B(int idd) : id(idd) {};
B(const B&) {};
virtual ~B() {};
void setid(int idd) {id = idd;}
void dummy() {cout << "B::dummy()" << " " << "id = " << id <<
endl;}
private:
int id;
};
template <class T, int size = 10>
class D1 {
public:
D1() : index(0) {};
D1(const D1&) {};
virtual ~D1() {};
T vt[size];
void push(T* i) {
vt[index] = *i;
index++;
}
void exvet() {
for(int i = 0; i<index; i++)
cout << "vt[" << i << "] = " << vt << endl;
}
void vetsize() {cout << size << endl;}
private:
int index;
};

int main()
{
D1<int, 5> od4; //First with int.
for(int i = 0; i<5; i++)
od4.push(&i); //<- Inserting ints in the array. Fine.
od4.exvet(); //<- Shows the ints int the array, Fine too.
D1<B, 10> od5; //Now with the programmer defined type.
for(int i = 0; i<10; i++)
od5.vt.dummy(); //<- !!!!!!!HERE!!!!!! It compiles and run!


Why shouldn't it? You've created an array of 10 B in od5.vt.
In my mind, when I declared "D1<B, 10> od5", I allocated
memory for 10 elements of the type "B" -- as I did when I
defined for the int type "D1<int, 5> od4".

In both cases, you've created an array of N elements. In the
case of int, the elements are not initialized (since int has a
trivial constructor), and reading the values before writing them
would be undefined behavior (in practice, you'll normally just
get a random value). In the case of B, the object has a user
defined constructor, and that will be used to initialize them.
(Given that the user defined constructor doesn't initialize B::id,
accessing B::id before setting it will still result in undefined
behavior.)
BUT, in the "int" case, I had to insert data in the vector
"vt" of the template in order to properly access it.

No. No more so than in the case of B.
Therefore, how could I access the members of "od5" if I did
not insert any class "B" object in the respective array "vt"?

The same way you could have accessed od4[ 3 ] before inserting
any data.
Wouldn't a more suited way for this case be:
D1<B, 10> od5;
for(int i = 0; i<10; i++)
od5.push(new B); //Inserting class B objects.

Why the new? (For that matter, why does push take a pointer?)
for(int i = 0; i<10; i++)
od5.vt.dummy(); //Accessing members of the objects inserted.


When you define an array, all of the elements are created. If
the array is of a non-POD class type, then its constructors are
called; otherwise, it has an indeterminate value; reading this
value is undefined behavior (unless the object has character
type).

It's possible to separate allocation from initialization, but it
requires a bit more work. The array itself must be declared as
unsigned char, and steps must be taken to ensure that it is
properly aligned. And placement new must be used for
initialization.
 
M

Marcelo De Brito

Hi, James!
Why the new? (For that matter, why does push take a pointer?)

It was just some implementation details for the code work a little
more without needing to debug it. It is just a code test and not the
final product. :)

Without the "(new B)" the code didn't compile.

By the way, thank you very much for your explanations! :)

Thank You!

Marcelo
 

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

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top