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

Class Constructors

Started by villiageidiot01 Jan 29, 2005 at 2:55 PM 15 replies 1.9k views
Original Post
villiageidiot01
villiageidiot01
In C++, is a constructor for a class simply there to set a "default value"? For example:

class CRect()
{
    int x, y; 
    public:
    CRect(int x, int y); // constructor?
};

CRect::CRect
{
    x = 5;
    y = 15;
}

So this basically makes a class CRect, and sets the values of x and y to 5 and 15 respectively. So if you did not modify those values somehow within the class, they would remain 5 and 15. Right? Is this the point of constructors...declaring a default value for protected integers, etc. within a class? Or am I off here? Also, what are destructors used for? Sorry for these dumb questions, I'm just a bit confused and am still rather new to C++.
v0dKA
v0dKA
Yes, a constructor is something that sets default values for your variables. This is especially useful for pointers, as you should always initialize them to NULL.

Destructors are typically used for freeing allocated memory from within the class. For example, if you allocated something with new, you always want to deallocate them somewhere with delete. A good place to do this is the destructor.
.:-v0d[KA]-:. <<>>
Fruny
Fruny
Initializing member data, allocating memory, opening files, establishing network connections, loading image data, setting graphics modes, registering the object with a global facility, starting a timer, acquiring a lock...

Anything that will make your object "usable".

And, as a side note, member initialization is properly done in the constructor's initializer list, rather than in its body. Otherwise, the member is first default-initialized (e.g. set to zero), and then assigned to, which often leads to performance penalties.

class CRect{    int x_, y_; public:    CRect(int x, int y);};CRect::CRect(int x, int y): x_(x), y_(y){}
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Scythen
Scythen
Constructors have many uses. This could be a several page explanation but due to time constraints I'll be briefe.

When you are first learning C++ the main role of the constructor is initialization of member variables.
class MyClass{    MyClass() : m_value(1)    {    }    int m_value;};


This initializes m_value with a known value.

Then you come to the copy constructor
class MyClass{    MyClass(const MyClass &rhs) : m_value(rhs.m_value)    {    }    int m_value;};

This defines what happens when an object is copied.

You can make specialized constructors to that allow you to initialize the class with data, for example
class MyClass{    MyClass(const char *inString) : m_value(atoi(inString))    {    }    int m_value;};

This allows the class to be initialized with a string.

You will now find however that the following will compile
void MyFunc(const MyClass &inClass){}int main(void){    MyFunc("10");}


In this case the constructor is providing a valid cast between the string and your class type. This can be both good and bad. If you don’t want a constructor to be usable as a cast, add the 'explicit' key word.
class MyClass{    explicit MyClass(const char *inString) : m_value(atoi(inString))    {    }    int m_value;};


Now you will be forced to explicitly call the constructor and the MyFunc example will result in a compile error.

You can have many overloaded versions of a constructor, depending on your needs. Also, members that are declared as const can only be modified in the constructor, after that they are const and can not be changed.

There are many other things to learn about constructors but lets move to destructors.

Destructors provide a mechanism for you to clean up. Say your class uses memory allocated with new, you can make sure its deleted in the destructor.
class MyClass{    MyClass()    : m_pValues(new int[10])    , m_numValues(10)    {    }    ~MyClass()    {        delete [] m_pValues;    }    int *m_pValues;    const int m_numValues;};


In some cases you may not be able to clean up after a class without using the destructor. This is a bit advanced but when an exception is thrown, the stack is unwound (all the functions return until a catch block). You can’t delete stuff or release things so you have to rely on destructors or write catch blocks.

Then there are virtual destructors, these are needed when you derive new classes from existing classes. In the case that a class is deleted through a base class pointer, the virtual function system will ensure that each derived class gets its destructor called.

There is much more that could be said on this topic but I'm out of time.
Trip99
Trip99
One caveat: if you do allocate memory etc. in your constructor you have to be able to handle an error situation e.g. ran out of memory. You cannot return a value from a constructor so the only (sensible) solution is to use exceptions. However in games a lot of people turn exception handling off due to it slowing down the code - so what can you do? For this reason I use constructors only to initialise member variables to defaults and I do nothing else that may cause an error. I then tend to have an Initialise function that can return if an error has occured.

You do then of course end up in the situation where you have to handle multiple calls to your initialise or even initialise never being called! I have never found a clean solution to this problem.
------------------------See my games programming site at: www.toymaker.info
Fruny
Fruny
Quote:
There is much more that could be said on this topic but I'm out of time.


Which reminds me...

And also...

links fixed, hopefully.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
v0dKA
v0dKA
Quote:
Original post by Fruny
Quote:
There is much more that could be said on this topic but I'm out of time.


Which reminds me...

And also...


Linkification error 0x00000001: Broken Link.
.:-v0d[KA]-:. <<>>
Fruny
Fruny
Fixed. Thanks.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Zahlman
Zahlman
Quote:
Original post by Fruny
And, as a side note, member initialization is properly done in the constructor's initializer list, rather than in its body. Otherwise, the member is first default-initialized (e.g. set to zero), and then assigned to, which often leads to performance penalties.


More importantly, it requires that such default-initialization is possible. Since defining a constructor for an object removes the default constructor, you then need to specify how to construct the object with no arguments in order for that to be doable. That leads to headaches down the line for people who don't know about this:

// "Hi, I'm Bob, and I don't know anything about initializer lists. Here's my code:"class Foo {  int x, y;  public:  Foo(int a, int b) { x = a; y = b; }  // Since there is a constructor here, the compiler will no longer generate a  // default Foo(). And since we didn't provide default values for a and b, we  // can't make a plain call "Foo()" to get this constructor.}class Bar {  Foo myFoo;  public:  Bar() { myFoo = Foo(3,4); }  // "Hey, why do I get an error for this?"  // That would be because of the implicit default-initialization Fruny mentioned.  // The compiler is trying to generate the code to default-initialize myFoo  // before the constructor code is actually run - but it can't find a Foo()  // definition.  // We fix it like this:  // Bar() : myFoo(3,4) {}}class Baz : public Foo {  public:  Baz() {}  // "Er... I guess this is the same problem, then."  // Yep. The bases of a derived class are part of its structure, and thus they  // need to be initialized too.  // "Okay, but how do I specify that? There's no myFoo member here..."  // Simple. Just do it as if there were a member whose name matched the name of  // the base class, like this:  // Baz() : Foo(3,4) {}}// Hey, you know, now that you know about initializer lists, you should probably// go back and fix your Foo class too.// "Ok, I think I can do that...class Foo {  int x, y;  Foo(int a, int b) : x(a), y(b) {}}// So far, so good; but there are two more tricks you should learn here.// First, the bits inside brackets are full C++ expressions; you can make them// more complicated than that (although you can't do statement logic). So you// can for example do bounds-checking...// "Wouldn't that require an if statement? I thought you said -// Hint: ternary operator. :) Anyway. The other thing is that the labels on the// brackets do need to match the members and base names, but within the// brackets, the scoping rules are different - anything in there would prefer// to be interpreted as one of the parameter names (or anything else that can// be found in the current scope) rather than one of the members. So you can// reuse the names:// Foo(int x, int y): x(x), y(y) {}// Pretty, yes?// "Neat-O! Thanks Zahlman!"
pex22
pex22
Destructors are called when the object is released from memory (if it is not a pointer, then it will be released in the end of function (like main)).
you can also use it for closing windows or files that the object has opened, note that destructors doesn't return a value too so dont call functions in them that returning their value is a must/useful.
pex.
Mr Lane
Mr Lane
A question about contructors and inheritence: I have a class that has its own constructor, and it is derived from another class that also has a constructor (its actually a wxWidgets base class). My question is, how does this work? What I mean is how do i provide values to the arguments to both my constructor and that of the base class? I thought this was the way how, but now I am not so sure:

MyClass::MyClass(int x, int y) : BaseClass(x){    //code goes here}


I was under the impression that this implementation of a constructor for my class will pass that value x to the constructor of the base class (assume it requires 1 int), but the compiler is complaining, so maybe this means something else?
Scythen
Scythen
Mr Lane,

It appears that you are correctly calling the base class constructor.
Without knowing what compile error is or being able to see the class definitions it’s really not possible to determine what the problem is.

A side note on the initializer list:

Some compilers require the members to be initialized in the order that they are declared in the class.
class MyClass{    MyClass()    : m_a(1)    , m_b(2)    , m_c(3)    // notice that members appear in the same order    {    }    int m_a;    int m_b;    int m_c;};

Most modern compilers will not care about the order, but I have no idea what you are using.

Another interesting fact is that the base class constructor is always called first, regardless of the order. So if you put the base class initializer last it will still be called first. All of the other initializers will be called in the order they are listed. Because of this fact the following will not work, although it will compile with no errors or warnings.
class MyClass : public MyBase{    MyClass(int value)    : m_a(value+1)    , m_b(m_a+1)    , m_c(m_b+1)    , MyBase(m_c+1)    {    }    int m_a;    int m_b;    int m_c;};

MyBase will be initialized with m_c before it is initialized. For the most part you should avoid using members in the constructor list like this example does.
Mr Lane
Mr Lane
My problem turned out to be unrelated :). Now i have yet another wxWidgets related one (its just one weird problem after another with wxWidgets!)

Thanks anyway.
Fruny
Fruny
Quote:
Original post by Scythen
Some compilers require the members to be initialized in the order that they are declared in the class.

Most modern compilers will not care about the order, but I have no idea what you are using.


The ordering of the initializer list is irrelevant.

It's only the order of declaration of members in the class that imposes the order of initialization. Good compilers will warn you if you try and list them in a different order, so as not lets you make incorrect assumptions.

Quote:
Another interesting fact is that the base class constructor is always called first, regardless of the order.


Correct. And in the presence of virtual bases and multiple inheritance, it becomes much more complicated...
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Scythen
Scythen
Quote:
Original post by Fruny
Quote:
Original post by Scythen
Some compilers require the members to be initialized in the order that they are declared in the class.

Most modern compilers will not care about the order, but I have no idea what you are using.


The ordering of the initializer list is irrelevant.

It's only the order of declaration of members in the class that imposes the order of initialization. Good compilers will warn you if you try and list them in a different order, so as not lets you make incorrect assumptions.


My post was unclear at best, I definitely didn't make clear which order I was speaking of, declaration order or initializer list order, sorry for the confusion.

You are absolutely correct, the initialization order is dictated by the declaration order, the construction list order is irrelevant except for on some compilers that will issue warnings and or errors if they do not match.

Topic Locked

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

Sign in to reply to this topic.