Initializing a struct

Hi,
just a little while ago I learned that it is possible to include the initialization for each element of a struct inside its definition. I think this is a great idea and I am doing it as follows (just to have it all here)
1
2
3
4
5
6
struct Data{
   Data():a(-FLT_MAX),
          b(-FLT_MAX){};
   flaot a;
   float b;
};

The above works fine for me but is there a way to do the same with a vector even though I might not know the number of elements in the beginning? Or is that an irrelevant question because it is not possible to append an element to a vector without defining its value (as push_back() will give an error)?
As soon as you create an object from a class the constructor/struct for that class/struct is called automatically.

That means if do something like this:

1
2
3
Data d; // Here the constructor on line 2 is called
std::vector<Data> v;
v.push_back(d); // The initialized Data object is copied here 

So this means that v.push_back() is a constructor for a new element of a vector as it "constructs" a new element?

Tanks.
No, it makes a copy of the parameter passed to it inside its internal array.
Hm, ok. I guess the last question was composed a little to quickly. I'll try again, this time a little more elaborate. Sorry!

So if Data d; calls the constructor it is clear that d.a and d.b are both set to -FLT_MAX but as std::vector<Data> v; will initialize a vector of size 0 there are no entries for v[i].a and v[i].b at all and I would get a out of range error as soon as I try to access any of them. So in this case the constructor would not be called (or ignored)? But I guess as long as I only use v.push_back(d) to grow my vector I should be save to always have a known state in the vector. In other words there is no need for a constructor for a vector.

The reason why I am concerned about this is that I need to expend the struct above to:
1
2
3
4
5
6
7
struct Data{
   Data(): surfaceA(-FLT_MAX),
           surfaceB(-FLT_MAX){};
   float surfaceA;
   vector<float> layerNode;
   float surfaceB;
};

All of the data stored is e.g. temperatures but the problem is that I may have a different number of layerNode's each time I run the program.

Would the above then be sufficient to guarantee that I don't end up with random entries in any instance of vector<Data> x provided I only use x.layerNode.push_back(d) to grow my vector?

Thanks for the great help!
Last edited on
You can call the vector's constructor like that. This will create a vector with size 5:
1
2
3
Data():surfaceA(-FLT_MAX),
          surfaceB(-FLT_MAX),
          layerNode(5) {};


If you don't know how many layerNode objects you are going to need, it's probably better to use push_back as you said.
Last edited on
The default constructor is used by the vector if you use the vector's constructor that takes the number of elements you want in it by default:
1
2
3
4
Date d;
d.a = 1.0;
vector<Date> dvec (3); //contains three default-constructed Dates
dvec.pus_back(d); //dvec now contains an additional Date with Date::a being 1.0 



Try this out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Test
{
    Test(){cout<<"Default Constructor"<<endl;}
    Test(const Test &from){cout<<"Copy Constructor"<<endl;}
    Test &operator=(const Test &from){cout<<"Assignment Operator"<<endl;return*this;}
    ~Test(){cout<<"Destructor"<<endl;}
};
//...
Test t;
{
    Test t2 (t);
    t = t2;
}
vector<Test> tvec (3);
tvec.push_back(t);
Watch the output and see if you can figure out which lines do what ;)
What about...
1
2
3
4
5
6
7
8
9
10
struct Data{
   Data(): surfaceA(-FLT_MAX),
           surfaceB(-FLT_MAX){
           layerNode.push_back(...);
           layerNode.push_back(...);
           };
   float surfaceA;
   vector<float> layerNode;
   float surfaceB;
};
Will it compile?
-Oh, L B said it all-
Last edited on
Ok, so I have tried what L B has suggested. The full code for my mini program is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
#include<vector>

using namespace std;

struct Test
{
    Test(){cout<<"Default Constructor"<<endl;}
    Test(const Test &from){cout<<"Copy Constructor"<<endl;}
    Test &operator=(const Test &from){cout<<"Assignment Operator"<<endl;return*this;}
    ~Test(){cout<<"Destructor"<<endl;}
};

int print(int i){
    cout << endl << i << ' ';
    return i+1;
}

int main(){
    int i=1;
    i=print(i); //1 value of i printed
    Test t;
    {
        i=print(i); //2
        Test t2 (t);
        i=print(i); //3
        t = t2;
    }
    i=print(i); //4
    vector<Test> tvec (3);
    i=print(i); //5
    tvec.push_back(t);
    i=print(i); //6
return 0;
}



The pint function is just to make it easier to see where in the code the output is generated. I am sadly out of time just now to completely understand what is doing what and why but I will spend some time on it tonight. But there are a bunch of new questions this has already brought up for me.

1) I know the code is just an example and therefor not intended to have a real function. However I find it sort of irritating that it is possible to build a struct such as Test. Is there any reason why it is possible? Essentially the struct does nothing. Shouldn't a struct always contain at least some data (or at least the possibility to do so)?

2) Is there any need to make an Assignment Operator? So far I have never defined that myself but it has always worked. So I assume it is here more for teaching purposes - thanks for including it - but there is no need to usually including it in "real live" struct's unless I want to overload (I hope I have finally understood the meaning of this) the = operator for this struct.

3) I have, so far never defined a destructor. I always assumed that it is only needed if I wish to distruct an instance before the end of its scope. Is that correct? If so I would assume that it is in here, again for teaching purposes - thanks again - but that all instances would be distructed in any case with when the end of main() is reached.

4) How would I call the destructor manualy? ~t?

Thanks for all the input!
Last edited on
1. You should set the memory you want to, in that struct, and use it however you want to, but you can just hold some functions inside.
Let's say you have a class called Math.
You prepare some functions (static ones) Add, Sub, Mul, Dev, Pow, Abs, Sin...
You will then be able to call Math::Add, Math::Sub...
2. No, he just explained some extra functionalities you may have liked.
3. A destructor gets called when you do not need anymore a class/struct.
4. Yes.
1. You can always make an empty class struct, or union.

2 & 3. If you use dynamic memory inside the struct/class, there are a few problems then with the default copy constructor, default operator=, and default destructor the compiler generates.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DynamicHolder
{
    int *p; //just as an example
    int size;
public:
    DynamicHolder()
    {
        p = new int[100];
        size = 100;
    }
    DynamicHolder(int num)
    {
        p = new int[num];
        size = num;
    }
    int &operator[](int pos)
    {
        if(pos < size) return p[pos];
        else return size; //just so the code compiles, not a good idea
    }
    int Size()const{ return size; }
};
1
2
3
4
5
//Problem 1: What does this do?
int main()
{
    DynamicHolder dh; //constructor makes it hold 100 elements
} //then it goes out of scope...but delete[] was never used! 
1
2
3
4
5
//fix: add this to DynamicHolder:
    ~DynamicHolder()
    {
        delete[] p;
    }
1
2
3
4
5
6
7
//Problem 2: What do these do?
int main()
{
    DynamicHolder dh1; //p = new int[100]
    DynamicHolder dh2 (50); //p = new int[50]
    dh1 = dh2; //dh1.p = dh2.p
} //dh2 uses delete[] on p, then dh1 uses delete[]...on the exact same memory! (==BAD!) 
1
2
3
4
5
6
7
8
//Fix: define your own operator=:
    DynamicHolder &operator=(const DynamicHolder &from)
    {
        delete[] p;
        size = from.size;
        p = new int[size];
        for(int i = 0; i < size; ++i) p[i] = from.p[i];
    }
1
2
3
4
5
6
7
8
9
10
11
//Problem 3: same as before but more subtle
void CrazyPrint(DynamicHolder dh) //no reference to prevent modifying original
{
    for(int i = 0; i < dh.Size()-1; ++i) dh[i] = dh[i+1]*2;
    for(int i = 0; i < dh.Size(); ++i) cout << dh[i]/3 << " ";
} //delete[] p
int main()
{
    DynamicHolder mydh; //p = new int[100]
    CrazyPrint(mydh); //dh.p = mydh.p
} //delete[] p...again! ahhh! 
1
2
3
4
5
6
7
//fix:
    DynamicHolder(const DynamicHolder &from)
    {
        p = new int[from.size];
        size = from.size;
        for(int i = 0; i < size; ++i) p[i] = from.p[i];
    }
So, if you use dynamic memory, this automatically means you MUST (should) define the copy constructor, operator=, and destructor yourself. The compiler will never guess and do these things for you.

4. Never call the destructor yourself. You only do that if you use placement new, and you would use placement new in extremely rare cases. But, the syntax is that of MyObject.~MyClass(); just in case you were curious. Destructors are called automatically for you when you use delete or delete[] on pointers, or when normal variables just go out of scope (eg end of a function)
Last edited on
1) I know the code is just an example and therefor not intended to have a real function. However I find it sort of irritating that it is possible to build a struct such as Test. Is there any reason why it is possible? Essentially the struct does nothing. Shouldn't a struct always contain at least some data (or at least the possibility to do so)?


Obviously the utility of such a construct is limited, but it does see use. Classes intended to be abstract base classes, for instance.


2) Is there any need to make an Assignment Operator? So far I have never defined that myself but it has always worked. So I assume it is here more for teaching purposes - thanks for including it - but there is no need to usually including it in "real live" struct's unless I want to overload (I hope I have finally understood the meaning of this) the = operator for this struct.


Sometimes there is, sometimes there isn't. By default one is generated for you. You only need to supply one if the default behavior of member-wise assignment isn't appropriate for an instance of your class. The most common example of this is when one of your members is a pointer that points to memory that was dynamically allocated via new. If you don't supply an operator=, which of the two objects now containing a copy of that pointer are responsible for freeing that memory?

http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three


3) I have, so far never defined a destructor. I always assumed that it is only needed if I wish to distruct an instance before the end of its scope. Is that correct? If so I would assume that it is in here, again for teaching purposes - thanks again - but that all instances would be distructed in any case with when the end of main() is reached.


You need a destructor any time there is something you need done when an instance of a class is destroyed, whether it's freeing a resource (closing a file, returning memory, releasing a lock) or passing a message to another object.

Never destruct an instance before the end of it's scope. That means you've got an accessible object hanging around with an indeterminate state, and accessing that object will result in undefined behavior.

Googling RAII may prove informative.
Last edited on
Topic archived. No new replies allowed.