Sign in to follow this  
fpsgamer

Design Pattern: Init &Tear Down Functions

Recommended Posts

Lets say I have a class similar to this:
class CTest{
public:
CTest(){}
~CTest(){}

Init(); // allocate memory
TearDown(); // deallocate memory and stuff
};






Now I would perform something like this when I create an object and when I am through with it... CTest ct; c.Init(); c.TearDown(); or similarly... CTest* ct = new CTest(); ct->Init(); ct->TearDown(); But the thing is I could have easily placed Init() and TearDown() bodies inside the Constructor and Deconstructor. Having them as seperate functions means I have to rely on the caller to remember to do both of thoes things. What design pattern should I follow? edit: More trouble can follow if a person decides to use a member function that would rely on Init() having been called. If they decide to use another function without calling Init() first that would lead to dangerous behaviour .... But the question is still the same ... what is the proper design pattern?

Share this post


Link to post
Share on other sites
Why not put what needs to be initialized in the constructor, and what needs to be deinitialized in the destructor? This eliminates the possibility of the user initialized more than once or less than once.

Share this post


Link to post
Share on other sites
I prefer to have explicit Init/DeInit functions. That way I can return error codes if necessary, and try to deal with init calls failing. You can always call your init/deinit functions from within your c-tors and d-tors.

Share this post


Link to post
Share on other sites
If it's possible at all put them in constructor / destructor. If it's not possible I'd prefer rethinking the design so that it can be done.
The name of the pattern is raii (Resource Acquisition Is Initialization) btw, or more broadly resource management.

EDIT: One benefit of raii is that, if done properly, there will be no leaks or whatever even in the face of exceptions.

Share this post


Link to post
Share on other sites
Quote:
Original post by agi_shi
Why not put what needs to be initialized in the constructor, and what needs to be deinitialized in the destructor? This eliminates the possibility of the user initialized more than once or less than once.


Thats what I thought, but I'm reading a book which has code examples/tutorials from "real programmers" exhibiting this pattern.

This seems like a very "C" thing to do and doesnt look like a good idea in "C++"

But more importanly should one have to design classes as though the people using them are going to be idiots? Or should they be idiot proof?

Share this post


Link to post
Share on other sites
Quote:
Original post by fpsgamer
Quote:
Original post by agi_shi
Why not put what needs to be initialized in the constructor, and what needs to be deinitialized in the destructor? This eliminates the possibility of the user initialized more than once or less than once.


Thats what I thought, but I'm looking at code examples/tutorials from "real programmers" exhibiting this pattern.

This seems like a very "C" thing to do and doesnt look like a good idea in "C++"

But more importanly should one have to design classes as though the people using them are going to be idiots? Or should they be idiot proof?


In my opinion if you code something in C++ to use by somebody else, you simply assume they are not idiots. Programming, and certainly in C++, is difficult enough. So whatever helps automate some tasks and reduce complexity is welcome for anybody. Just look at how many bugs exist in software, I think raii helps a lot and it can be done in a good way in C++.

Share this post


Link to post
Share on other sites
It only makes sense to separate the initialization from the constructor if, in the course of normal operation, it's reasonable for the initialization to fail. The text book example is the file object, where opening the file can reasonably fail in the course of normal operation. Otherwise, if the initialization is expected to succeed and the initialization is required before using any of the member functions, then there's no reason to separate initialization from construction.

Share this post


Link to post
Share on other sites
Quote:
Original post by Driv3MeFar
I prefer to have explicit Init/DeInit functions. That way I can return error codes if necessary, and try to deal with init calls failing. You can always call your init/deinit functions from within your c-tors and d-tors.


Are the problems that your return codes indicate exceptional? As in, are they an exception to what should happen?

In that case you use exceptions:

class a { public:
a(): b(makeC()) {
if (b == NULL) // oh no!
throw std::runtime_error("b == NULL"); // throw exception
}

~a() {
// b will be freed by the auto_ptr when the auto_ptr gets destroyed
}

private: std::auto_ptr<c> b;

...
};

int main() {
try { // check for exceptions
a A;
A.doAwesomeStuff();
}
// catch std::exception s. note: std::runtime_error is an std::exception
catch(std::exception& e) { std::cout << e.what(); }
// catch anything else
catch(...) { std::cout << "unknown error"; }
}






Read More.

Share this post


Link to post
Share on other sites
And then you end up with a half-initialized object, if you are not very very careful.
See:
http://www.awprofessional.com/content/images/020163371x/supplements/Exception_Handling_Article.html

Share this post


Link to post
Share on other sites
Quote:
Original post by agi_shi
Quote:
Original post by Driv3MeFar
I prefer to have explicit Init/DeInit functions. That way I can return error codes if necessary, and try to deal with init calls failing. You can always call your init/deinit functions from within your c-tors and d-tors.


Are the problems that your return codes indicate exceptional? As in, are they an exception to what should happen?

In that case you use exceptions:
*** Source Snippet Removed ***

Read More.


I am well aware of exceptions and their uses. However, there are times when I don't like to deal with the overhead they carry with them, especially in cases where an exception would rarely be thrown (since you have to take the performance hit from exceptions regardless of whether one is thrown or not).

Share this post


Link to post
Share on other sites
The proper design pattern depends on your project guidelines. If you are setting your own guidelines, do as you wish. There are many reasons for having Initialize and Shutdown outside of the con/destructor, so I will provide a few:

Some people feel constructors should only zero-initialize. In such scenarios, a constructor will not be permitted to call any functions (not even new/malloc). Generally a destructor in this scenario would only contain a call to Shutdown().

A con/destructor will not be called if an object is allocated with malloc. This adds unnecessary restrictions to memory management.

Shutdown() can be called prior to an object falling out of scope.

Initialize() and Shutdown() may allow a minimal object allocated which only grows to it's needed size when in use. This would be combined with usage tracking, so Initialize would increment usage count, and Shutdown would decrement.

Certain information may not be available at allocation, but would be available during initialization. Imagine two objects which need to have a pointer to one another as part of initialization. The first object will be allocated prior to a pointer existing for the second object.

Sometimes you will have a time delay required between stages of initialization/shutdown, so in order to prevent a wait you have multiple functions which can be intelligently scheduled to prevent waiting.

Share this post


Link to post
Share on other sites
It's certainly reasonable to have objects that allocate "more" stuff during later calls.

However, if the user fails to Init() the object, I would suggest that crashing is a great way of telling the user he screwed up. The earlier you crash, and the more assured the crash is, the better. One way of forcing a crash on bad behavior, is to use assert(), although that goes away in release mode.

After all, if the program crashes, it's pretty obvious you did something wrong, and need to fix it. The easier the crash is to diagnose (Oh, 'm_internalPtr' is NULL -- let's see, what's supposed to set it to not-NULL?) the better.

However, I would recommend that terdown happen in the destructor -- it's too easy to forget otherwise. An alternative is to have a virtual Dispose() call, which tears down and then calls 'delete this' at the end, and makes the destructor private. That pattern, coupled with object factories, avoids mis-matched allocations/deallocations in the face of overridden operators new and delete.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
"The text book example is the file object, where opening the file can reasonably fail in the course of normal operation"

It depends on your project guidelines. In my projects, if a critical file is supposed to be there and I try to open it and it's not there, then I throw an exception. If I try to open a file which I know could possibly not be there,
my code would look something like:


if (File::Exists(filePath)
textFile = File::Open(filePath);


And in this case as well, if opening the file fails, it should also throw an exception.

Share this post


Link to post
Share on other sites
Just use a ctor/dtor until you prove a need otherwise. And even then, you will often want to call the init/shutdown from the ctor/dtor.

Share this post


Link to post
Share on other sites
You also should be wary of virtual function calls made from within ctor/dtor. That alone is sometimes good enough reason to stray from the useful-almost-all-the-time pattern of using the ctor and dtor for the purpose they were intended for.

Share this post


Link to post
Share on other sites
Quote:
Original post by Driv3MeFar
(since you have to take the performance hit from exceptions regardless of whether one is thrown or not).


Have you actually looked at the overhead of exceptions in the not thrown case? On x86 with MSVC it's along the lines of two stores per destructable object. This is comparable, and often less, than the overhead of explicit error-code handling. On other platforms the overhead is often even less.

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