Sign in to follow this  
kelcharge

Constructors and destructors of a Class?

Recommended Posts

Once again I am using C++ and I am trying to learn how to make a class. I get everything but the constructors and destructors. I have no clue how to use them or what they are for. So if anyone has some general information about constructors and destructors that would be much appreciated?

Share this post


Link to post
Share on other sites
The constructors and destructors, as their name imply, are used to create and destroy an instance of a class. Take the following example:


class Point {
private:
int* _x;
int* _y;
int* _z;

public:
Point() {
_x = new int(0);
_y = new int(0);
_z = new int(0);
}

Point(const int& x, const int& y, const int& z) {
_x = new int(x);
_y = new int(y);
_z = new int(z);
}

~Point() {
delete x;
delete y;
delete z;
}

//Various public methods...
}




Now, obviously, this particular implementation of a point is stupid since there is no need to use dynamic allocation for the members. I did it, however, to show you an area where constructors could be useful. Note that I have 2 constructors, which take either no arguments (defaults coordinates to zero) or 3 arguments specifying the coordinates.

The destructor is then used to clean the memory that was allocated dynamically during construction.

Note that if you do not provide constructors and destructors, default ones are generated. you would then use the Point class as follow:


int main() {
Point p1; //0 parameter constructor called;
Point p2(1, 1, 1); //3 parameters constructor called;
Point* p3 = new Point(); //0 parameter constructor called;
Point* p4 = new Point(2,2,2); //3 parameter constructor called;

delete p3; //Destructor called for p3;
delete p4; //Destructor called for p4;

//p1 and p2 exit scope here, destructors called.
}


Share this post


Link to post
Share on other sites
Constructors can be thought of as functions that are performed when an object is created. For example:


class Foo()
{
// constructor
Foo(){cout << "Instance of Foo has been created!\n";}
// deconstructor
~Foo(){cout << "Instance of Foo is being destroyed!\n";}
void Bar();
};


When you create an instnace of Foo, that string will be put out to the console. Deconstructors are pretty much the same except they perform functions when delete is called upon the class.

Share this post


Link to post
Share on other sites
Ok. You make classes for a reason, if you don't have a reason to group the data together in a logical whole, then there's no point in making a class right? I mean no one ever makes a class call FiveUnrelatedVariables. So the values of the objects variables have to be related to somehow. The constructor's job is to make sure that the values in the object meet that relationship. Some classes this is pretty easy, and the constructor doesn't do much. Other classes, the constructor needs to do more. Basically after the constructor runs, the object should be in a valid state for any of the member functions to be called.

The destructor's job is to dispose of any resources the object owns. For example, the object may have grabbed a chunk of memory or opened a file. The destructor then will either free that memory or close the file.

Share this post


Link to post
Share on other sites
So if I make a class to define tiles that I am putting into a map. In the constructor of the class it should open the file to the image of the tile? How would the destructor of this then close the file?

EDIT: I am using Allegro, and I have it open the file like so.


class CTile {
public:
BITMAP tile; //this is the bitmap it is loaded on
string file;
void SetPos(int cval, int rval); //sets the values of c and r
void Displaycr(void); //just displays the values
private:
int c; //columns
int r; //rows
};



Now I get what should go into the constructor,
tile = load_bitmap(file);
, but how should I make the constructor within the class. Also how should the destructor be made?

Share this post


Link to post
Share on other sites

class Foo
{
Foo(){/*open file*/}
~Foo(){/*close file*/}
};

int main()
{
...
Foo myFoo = new Foo(); // Open the file
... // do what you gotta do
delete Foo; // File is closed based on the code in the deconstructor
...
}

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
... I mean no one ever makes a class call FiveUnrelatedVariables. ...


Unfortunately, people do make classes that could be named CollectionOfUnrelatedVariables. How often do we see posts that say, "I have this class that contains all my global variables ...".

Share this post


Link to post
Share on other sites
Quote:
(Modified) Original post by Ekim_Gram

class Foo
{
Foo(){/*open file, initialize bitmap with all the file data, and close it*/}
~Foo(){/*clean up bitmap*/}
};

int main()
{
...
Foo* myFoo = new Foo(); // on the heap
Foo bar; // on the stack
... // do what you gotta do
delete myFoo; // gets cleaned up
...
} // at end of scope, the "bar" gets cleaned up because it was a local.


Probably a bit more realistic this way :)

Anyway, it depends a lot on what exactly a BITMAP is, and what the relation is between tiles and the bitmap data. In particular, I really don't think "c" and "r" are rows and columns as in a *count*; they might be a row and column ID as in a *position* in the bitmap, though. A Tile object, logically, represents *one* tile. The whole set of tiles is represented by the whole set of objects, held in a container somewhere.

Here is one possible setup. It supposes that each Tile has a name indicating which bitmap file holds its image, and row and column values indicating where on the bitmap the actual Tile is. It will store the bitmaps separately, in a std::map - a logical "mapping" from the name to the file - and when a Tile is constructed or destructed, it may load or unload (and update the map accordingly) bitmap images, according to a reference count (it tracks how many Tiles currently use the image, and unloads it when all tiles using it have been destroyed). This is not tested, and deliberately not complete - I don't know exactly how you load bitmaps so I will just indicate where that is done, and you will have to figure out which headers you need to include ([google] is your friend here) and debug it.

If BITMAP does not have a default (i.e. no-argument) constructor, this will take some hacking to make it work - let me know if you run into problems.


class Tile {
// Members of a class default to private, thus my convention is to put private
// stuff first.
static std::map<std::string, std::pair<BITMAP, int> > images;
// Several things to notice here. The map is a templated type, as is the
// "pair"; read it as "map OF string TO (pair OF BITMAP AND int)".
// std::pair is just a quick way of making a struct with two data members,
// basically. Here the BITMAP is the bitmap, and the int is a "reference
// count" - the number of constructed Tile objects using this bitmap.
// Finally, the "static" means that there is one *shared* instance of the map
// for the whole class, rather than one per Tile object - this is how it
// should be.
// This design has the Tile class managing the Tile objects. I find this sort
// of design to be clean and compact; however, there are good arguments for
// factoring out the management to a separate class, too. (I just don't happen
// to agree with those arguments most of the time. ;) )
std::string myImage; // a "key" for the map.
int row, column; // position in the indicated bitmap for drawing.

// I will also need a private helper function, that will become clear later...
void dispose() {
// Decrements the reference count for the current object's BITMAP, to say
// "I am not using this bitmap any more."
std::pair<BITMAP, int>& theBitmap = images[myImage];
--theBitmap.second;
if (!theBitmap.second) { // that was the last user, so we must
// unload the image now.
cleanupBitmap(theBitmap.first);
// Warning! Don't try to invoke ~BITMAP directly yourself; it's bad mojo.
// What you probably want to do is just something like "theBitmap.first =
// BITMAP();", i.e. assign a blank BITMAP to it, and let the BITMAP
// assignment operator take care of things for you.
// And now, remove the item from the map.
images.erase(myImage);
}
}

public:
// And now, the public functions.
Tile(std::string imageName, int r, int c) : myImage(imageName), row(r), column(c) {
// Notice how I used an initializer list (google them - possibly with
// "site:gamedev.net" - if that's not familiar to you) to set up the data
// members, since they can just be copied across.
// Now, we do the magic part of possibly loading a bitmap.
// The operator[] on a map is a lookup-with-side-effect; it will return
// a default-constructed "value" (here, a pair of int and BITMAP, i.e. the
// the int will be 0 and the BITMAP will be whatever it is) if the "key" is
// not found, and otherwise returns the matching "value". In our case, if
// the key is not found, we want to insert anyway, so this is OK. (Note that
// the value is returned by non-const reference, so we can then modify the
// value in the map directly.) Note how I store to a reference, rather than
// a value, in order to preserve that modifiability over a few lines of code
// :)
std::pair<BITMAP, int>& theBitmap = images[myImage];
if (!theBitmap.second) { // the int is 0; i.e. we need to load
theBitmap.first = createBitmap(myImage);
// the createBitmap function should open the named file, do the actual
// creation, close the file (that can happen implicitly, e.g. if you use
// an ifstream object, it will close automatically at the end of scope)
// and return the BITMAP.
}
++theBitmap.second; // In any case, there is now one more Tile using
// that image than there was before.
}
void drawSelf(int x, int y) {
// Method to draw the tile. Note we are guaranteed that the bitmap is loaded
// and its name is in the map, because that was handled in the constructor.
// So again we can just use the operator[]:
BITMAP b = images[myImage];
drawImageChunk(x, y, b, row, column); // Again, this is yours to implement
}
~Tile() {
// The destructor. When we destruct a tile, we have to decrease that
// reference count for its image (since there's now one fewer tile kicking
// around that uses it), and unload the image if appropriate.
dispose();
// Because all the per-object data consisted of primitives (int values) and
// a std::string (which knows how to clean itself up), there is no more to
// do here. Objects containing pointers are trickier, however - normally you
// must delete (or delete[]) them if you did new (or new[], respectively)
// them in the constructor.
}
// Any time you need any of (destructor, copy constructor, assignment
// operator), you generally need all three. This is no exception :)
Tile(const Tile& other) : myImage(other.image), row(other.row), column(other.column) {
// This is a copy constructor, called when you initialize a Tile from
// another one. When that happens, we know that the map has the image loaded
// already, but we need to increment the refcount because we added another
// object using that image.
// So we just copy the members (using the initializer list again), and then:
++images[myImage].second;
}
// It is common to implement assignment operators in terms of the copy
// constructor, whenever there is complicated memory management to do. Here,
// that's not the case, so we don't need to create a temporary like that.
Tile& operator=(const Tile& rhs) {
myImage = other.image;
row = other.row;
column = other.column;
// Since this is an already-existing object, we need to decrement the
// reference count for that old image, and possibly unload it.
dispose();
// Notice that even though dispose() contains all the code invoked in the
// destructor, I factored it out into its own function - again, calling a
// destructor manually is bad mojo. (There is one case in which that should
// be done, but it is a part of some deep magic that beginners should not
// attempt - if curious, you can look it up in the C++ FAQ Lite.)
++images[myImage].second;
}
}

Share this post


Link to post
Share on other sites
I have a problem.


//Lets say within my class, in public:, I put
CTile(const char* filename);
//Then
CTile::CTile(const char* filename) {
tile = load_bitmap(filename);
}



Shouldn't that make it load the image?

Share this post


Link to post
Share on other sites
hi,
what you have received till now is the technical part of constuctors and destuctors and its is very right also , but let me tell you when to use it in order to make your code optimised.
As we know that as soon as an object is made the constructor is called depending upon the type of arguments being used to make the object, so keep whatever you want to initialise only once within the constructor as it will be called automatically so you dont have to worry about calling a seperate function for initialising as C++ classes does not allows you to intialise anything at their defination itself, so here lies the problem, the constructor code is optimised with the compiler as well so if u use constructor instead of a function to initialise than you will have optimal performance, for making a copy of an object already intialised you have the liberty of copy constructor.
Now, as you allocate some memory you need to realease it also after its use , so the best place to do it is in destructor as it is called anyhow , and you dont have one more thing to remember , which you need to do, otherwise you will have memory leaks and its like a slow poison, mail me if you need details of any of the above explanations.

Share this post


Link to post
Share on other sites

class CTile {
public:
BITMAP tile; //this is the bitmap it is loaded on
string file;
void SetPos(int cval, int rval); //sets the values of c and r
void Displaycr(void); //just displays the values
CTile(const char* filename);
private:
int c; //columns
int r; //rows
};

CTile::CTile(const char* filename) {
tile = load_bitmap(filename);
}
//Then
CTile tile1("whatever.bmp");



Would that be how I call the constructor?

Share this post


Link to post
Share on other sites
No; it means that any time you dynamically allocate an object (of whatever type) with "new", you must later (after it has served its purpose) dispose of it with "delete" (otherwise you have a memory leak; if you repeatedly do this, your program progressively eats up memory, because it doesn't know that it can let go of any of it) exactly once (otherwise you have undefined behaviour the second time around, because you're trying to do something to memory that isn't "yours" any more).

It can be tricky to get right, which is why you should use tools that manage the work for you - like the standard library containers for example.

Share this post


Link to post
Share on other sites
whats the difference between these two lines? in terms of p1 & p3?

Quote:

Point p1; //0 parameter constructor called;
Point* p3 = new Point(); //0 parameter constructor called;


Is it simply semantics? or do (other than the actual pointer address) p1 and p3 differ?

Is there any reason or circumstances where you would use one over the other, and why?

Thanks :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Barking_Mad
whats the difference between these two lines? in terms of p1 & p3?

Quote:

Point p1; //0 parameter constructor called;
Point* p3 = new Point(); //0 parameter constructor called;


Is it simply semantics? or do (other than the actual pointer address) p1 and p3 differ?

Is there any reason or circumstances where you would use one over the other, and why?

Thanks :)


The first line creates a p1 on the stack. p1 will be automatically destroyed when you leave scope. The second line creates a new Point instance somewhere in heap memory and then puts a pointer to that memory in p3. Doint it this way requires you to explicitly destroy p3 using the delete operator. Also, since p3 is a pointer, you must use the "->" operator to access it's members instead of using the dot "."

Point p1;
Point* p3 = new Point();

int p1x = p1.x;
int p3x = p3->x;

delete p3;

Share this post


Link to post
Share on other sites
Just though I'd mention, Kelcharge -- by your use of BITMAP and the load_bitmap function, it looks like you may be using the Allegro game library. It can be dangerous to call Allegro functions from constructors and destructors, because you always must call install_allegro() before anything else will work, and in the case of load_bitmap(), you also have to call set_gfx_mode() before it will work. So if you end up making a CTile before the initialization functions get called, Bad Things(tm) _WILL_ happen. Allegro and C++ need care to work together properly, since Allegro was designed as a C library.

Oh, and if you are indeed using Allegro, _NEVER_ use 'BITMAP tile' (or any other name); the makers intend for you to use 'BITMAP *tile' (note the '*'), because all the bitmap functions operate on and return type 'BITMAP *'. That lets Allegro take care of all the icky memory work, and lets you use create_bitmap() and destroy_bitmap() and such.

Au revoir,
Twilight Dragon

Share this post


Link to post
Share on other sites
Ok, it seems that that was only part of my problem. When I make the constructor it has to be defined inside the class or it doesnt work. Also, if I delete the tile in the destructor it automatically does it before the program fully loads so my map is empty? I am confused.....

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this