Skip to main content
gamedev.net
🔒 Locked

Problems when implementing exception.

Started by Nacho Aug 1, 2002 at 9:03 AM 13 replies 350+ views
Original Post
Nacho
Nacho
Hi! Can somebody help me with a problem with exceptions? Ok, here´s the situation. I´ve got a class that is in charge of building (validating) the DirectDraw objects passed through its methods. I don´t know if this is good design or not but my error-handling approach works like this: The methods that are in charge of validating the DirectDraw objects check internally if the function calls were successful (for example, DirectDrawCreate() ). If not they throw an exception in the following way: HRESULT hDDRetVal = DirectDrawCreate(NULL,&lpDD,NULL); if(DDERR_DIRECTDRAWALREADYCREATED==hDDRetVal) { throw DDObjErr( "CVideoObjectsFactory::BuildDDObject", "DD Object for this driver already created"); } // End if. // More if´s checking for the rest of the return values. This snippet is the beggining of the method int CVideoObjectsFactory::BuildDDObject(LPDIRECTDRAW4 &lpNewObj). CVideoObjectsFactory has a declared instance which is a private member of the class that deals with all the video stuff: CVideo. There are several exception objects inside CVideoObjectsFactory (one for each class of exception that might be raised in the building methods: surfaces, palette, clipper, etc). The DDObjErr from the last snippet is: class DDObjErr { public: DDObjErr(char *lpcErrorPlace,char *lpcError); ~DDObjErr(); char *GetErrorPlace(); char *GetError(); private: char *lpcErrorPlace; char *lpcError; }; The methods´ definitions are quite simple: CVideoObjectsFactory::DDObjErr:: DDObjErr(char *lpcErrorPlace,char *lpcError) { int cLength = strlen(lpcErrorPlace); DDObjErr::lpcErrorPlace = new char [cLength]; cLength = strlen(lpcError); DDObjErr::lpcError = new char[cLength]; strcpy(DDObjErr::lpcErrorPlace,lpcErrorPlace); strcpy(DDObjErr::lpcError,lpcError); } ////////////////////////////////////////////////////////////////////////////////////////////////// CVideoObjectsFactory::DDObjErr:: ~DDObjErr() { delete [] lpcErrorPlace; delete [] lpcError; } ////////////////////////////////////////////////////////////////////////////////////////////////// char *CVideoObjectsFactory::DDObjErr:: GetError() { return lpcError; } ////////////////////////////////////////////////////////////////////////////////////////////////// char *CVideoObjectsFactory::DDObjErr:: GetErrorPlace() { return lpcErrorPlace; } I have written a try block in the first layer of my program (inside WinMain): try { InitApplication(hWnd,Video); } // End try. catch(CVideoObjectsFactory::DDObjErr theException) { Video.ExceptionalShutdown(); MessageBox( hWnd,theException.GetError(), theException.GetErrorPlace(),MB_OK | MB_ICONEXCLAMATION); return 0; } // End catch. Inside InitApplication() I call the method for building the DirectDrawObject. CVideo::ExceptionalShutdown() releases all the DDraw related interfaces. Well, this is the introduction. I hope my explanation has been clear. Now the real problem: remember the first code snippet? Well, inside that place I throw an exception on purpose just to check all the exception-handling system: throw DDObjErr( "CVideoObjectsFactory::BuildDDObject", // String used for the caption of the msg box. "DD Object for this driver already created"); // String used for the text inside the msg box. The compiler complains while executing the program with a Debug Error Window after the constructor for the exception object returns and the exception is about to be thrown. I´m using MSVC. Any idea of what´s going on here? I can post more code / details if you want, but I think I have mentioned all the relevant stuff. Another issue: do you think this approach is correct? While designing the app I decided that it was not a good idea to leave error handling for the user of the class. Anyway, what do you think about it? Thanks in advance!!
Stoffel
Stoffel
Well, first of all, I highly recommend you derive your exception from one of the std::exception classes (you can find them all in "stdexcept"). This will allow you to just have a single "catch std::exception &x" if you want to catch _all_ exceptions, including your own. You can catch specialized exceptions in handlers before this one if you want to handle different errors differently.

Next, I always catch my exceptions by reference. Your catch handler will actually create a copy of the exception, and you won't be able to use polymorphism in the catching.

Other than that, I take it the problem is that you're not catching and you expect to? Try to ignore the error and put a breakpoint in your catch handler, see if it fires. If it does, then you probably have your debugger setup to stop on all exceptions--the debugger's just trying to be handy in letting you know the exact moment when an exception occurs, in case it's hard to figure it out from the handler. There should be a way to disable this behavior if it bugs you.

[edited by - Stoffel on August 1, 2002 10:56:33 AM]
Stoffel
Stoffel
''k, responding to myself.

Looking at your code, the fact that the exception is being passed by value in your catch statement _IS_ the source of the problem, and you''re probably getting a memory access violation. Your original exception object is destroyed before the catch handler is called, meaning the memory it points to is deleted. The object''s then copied (not quite sure how it gets copied AFTER the original is destroyed, but that appears to be how it works), and the two char pointers now point to freed memory. Voila, access violation.

Solution(s):
- Pass by reference.
- Use std::string instead of char* and you can leave it as pass-by-value; your copy ctor will "do the right thing" and perform a deep copy of the strings (actually probably ref-count the string up & down).

I''d do both of these so you don''t have to write a dtor for your class (automatic) and you want to pass by reference to avoid copying and allow polymorphic exceptions.
Crazy_Vasey
Crazy_Vasey
quote:

Well, first of all, I highly recommend you derive your exception from one of the std::exception classes (you can find them all in "stdexcept"). This will allow you to just have a single "catch std::exception &x" if you want to catch _all_ exceptions, including your own. You can catch specialized exceptions in handlers before this one if you want to handle different errors differently.



Isn''t that what catch(...) does?
Stoffel
Stoffel
Yes, but you don't have access to the exception object. If you derive all your exceptions from std::exception and implement the what () method, you can get a string from any one.

PS, I wrote up this little example to show the life-time of an exception when you're catching it by value:

      
#include <iostream>

using namespace std;

class Exception
{
public:
Exception () { cout << this << ": ctor" << endl; }
~Exception () { cout << this << ": dtor" << endl; }
Exception (const Exception &org) { cout << this << ": copied from " <<
&org << endl; }
};

int main ()
{
try
{
Exception x;
cout << "Throwing exception: " << &x << endl;
throw x;
cout << "No way to get here" << endl;
}
catch (Exception x)
{
cout << "Exception caught: " << &x << endl;
}
return 0;
}

output:
0012FF5C: ctor
Throwing exception: 0012FF5C
0012FF54: copied from 0012FF5C
0012FF58: copied from 0012FF54
0012FF5C: dtor
Exception caught: 0012FF58
0012FF58: dtor
0012FF54: dtor

I'm not sure why there seems to be one extra exception here (0x0012FF54??). Any gurus out there know? BTW, here's the output when catching by reference:
0012FF5C: ctor
Throwing exception: 0012FF5C
0012FF54: copied from 0012FF5C
0012FF5C: dtor
Exception caught: 0012FF54
0012FF54: dtor

Still a copy. Weird. This of course means that merely passing by reference won't fix your problem--you have to make sure your object is copied correctly, which it currently isn't.

[edited by - Stoffel on August 1, 2002 11:30:30 AM]
Nacho
Nacho
Thanks for answering to my post! I´ve a dial up connection, so I´ll read your replies off-line and I´ll connect again in some hours! Thanks!
Dactylos
Dactylos
quote:
Original post by Stoffel
I''m not sure why there seems to be one extra exception here (0x0012FF54??). Any gurus out there know?

[...]

Still a copy. Weird.


I''m not exactly a guru, and I''m not sure about this, but I think the extra copying is because the thrown object must be allocated somewhere besides the stack, since the stack is being unwound. So throwing an object allocated on the stack causes it to be copied to this ''exception area''. Then the stack is unwound until a handler is found, and in the first case (pass-by-value) one more copy is performed from the ''exception area'' to the handler''s object on the stack.
Void
Void
Lookup and understand RAII (Resource Acquistion Is Initialization) idiom if you want to use exceptions correctly.
Nacho
Nacho
I tried making the exception a reference but the problem persisted. Besides, in the book I´ve read, the example on exception didn´t pass the exception as a reference. Void: What is RAII?

To the rest of you: Do you think this approach it´s worth it? Or maybe it´s better to keep is simple and test with if statements the return code and act properly based on them? What´s your opinion about this?
Stoffel
Stoffel
Exceptions are worth it. You need to make sure your exception object copies correctly. It currently doesn''t. If you read my last post, I told you making it a reference would get rid of one of the copies, but not all of them. Therefore, you need your object to be copied correctly, or you''ll get a crash when you try to throw.
msn12b
msn12b
quote:
Original post by Stoffel
Yes, but you don''t have access to the exception object. If you derive all your exceptions from std::exception and implement the what () method, you can get a string from any one.

PS, I wrote up this little example to show the life-time of an exception when you''re catching it by value:


        
#include <iostream>

using namespace std;

class Exception
{
public:
Exception () { cout << this << ": ctor" << endl; }
~Exception () { cout << this << ": dtor" << endl; }
Exception (const Exception &org) { cout << this << ": copied from " <<
&org << endl; }
};

int main ()
{
try
{
Exception x;
cout << "Throwing exception: " << &x << endl;
throw x;
cout << "No way to get here" << endl;
}
catch (Exception x)
{
cout << "Exception caught: " << &x << endl;
}
return 0;
}

output:
0012FF5C: ctor
Throwing exception: 0012FF5C
0012FF54: copied from 0012FF5C
0012FF58: copied from 0012FF54
0012FF5C: dtor
Exception caught: 0012FF58
0012FF58: dtor
0012FF54: dtor

I''m not sure why there seems to be one extra exception here (0x0012FF54??). Any gurus out there know? BTW, here''s the output when catching by reference:
0012FF5C: ctor
Throwing exception: 0012FF5C
0012FF54: copied from 0012FF5C
0012FF5C: dtor
Exception caught: 0012FF54
0012FF54: dtor

Still a copy. Weird. This of course means that merely passing by reference won''t fix your problem--you have to make sure your object is copied correctly, which it currently isn''t.

[edited by - Stoffel on August 1, 2002 11:30:30 AM]

I think throw x is equivalent to return x, causing a copy to be thrown rather than the original.

Of course, I don''t really know, but you can post on comp.lang.c++.moderated to get a better answer.

MSN
Void
Void
quote:
Original post by Stoffel
Still a copy. Weird. This of course means that merely passing by reference won''t fix your problem--you have to make sure your object is copied correctly, which it currently isn''t.



You are seeing two copies of the exception object because you are creating it in the stack. That becomes a local variable and gets unwind like a normal stack object.

ie.

try
{
Exception x; // this is another object that gets unwind when throw occurs
throw x;
}


You should just throw straight.

try
{
throw Exception();
}


Ignacio: Your problem here is not about exception only. It''s about shallow vs deep copying for classes.

Read "An exceptional quest" on gamedev for more info about RAII or search google, but it''s a unrelated issue to your problem.
Stoffel
Stoffel
Right you are, Void. Throwing that directly means there are no copies.

I re-re-reiterate, this does not mean that the original poster should just do this and consider his program "fixed". The exception object that''s been written does not copy correctly, which needs to be fixed or this problem will crop up in the future.
dalleboy
dalleboy
Also:
int cLength = strlen(lpcErrorPlace);
DDObjErr::lpcErrorPlace = new char [cLength];
Should be:
int cLength = strlen(lpcErrorPlace);
DDObjErr::lpcErrorPlace = new char [cLength + 1 ];
And then there's the "the rule of three" - if you write any one of a copy constructor, copy assignment operator or destructor, then you will almost certainly need to write all three for your class to function properly.

[edited by - dalleboy on August 5, 2002 6:00:08 PM]
Nacho
Nacho
Sorry for not having posted for some days. I´ve to finish an assignment in a couple of weeks so that kept me pretty busy. I´ve chosen not to use exceptions for my current project, but I´d like to understand them anyway. In the next few days I´ll try to implement a copy constructor for the class and tell you about the results. Thanks for your replies!

Topic Locked

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

Sign in to reply to this topic.