SFML community forums
General => Feature requests => Topic started by: Haikarainen on July 03, 2011, 06:03:47 am
-
Why not create a sf::Exception to make SFML-usage much more flexible and easier to debug?
Take sf::Image for example, if you try to load an image that doesn't exist to it using sf::Image::LoadFromFile();, all it does is print "Error: File blablabla". It should throw an exception instead, so you can handle your errors appropriate to your actual project.
Like if you have a custom error-log-management, you could do this:
try{
sf::Image Bleh;
Bleh.LoadFromFile("./data/bleh.jph");
sf::Image Bleh2;
Bleh.LoadFromFile("./data/bleh2.jph");
}catch(sf::Exception &e){
myErrorManager << e;
}
instead of:
sf::Image Bleh;
if(!Bleh.LoadFromFile("./data/bleh.jph")){
MyErrorManager << "Couldn't load bleh from bleh.jph, this error is hardcoded and not very flexible.";
}
sf::Image Bleh2;
if(!Bleh2.LoadFromFile("./data/bleh2.jph")){
MyErrorManager << "Couldn't load bleh2 from other file bleh2.jph, this error is hardcoded and not very flexible.";
}
I was gonna post this as an issue/feature/request on the bugtracker, but didn't know how to approach that since i never used something like that before :P
-
Exceptions don't work well with shared libraries, that's why SFML doesn't use them. And they would be hard to translate to other languages such as C.
-
Exceptions don't work well with shared libraries, that's why SFML doesn't use them. And they would be hard to translate to other languages such as C.
Excuse my stupidity, but why wont they work well with shared libraries? You wouldn't have to translate them to other languages, just keep them to the most supported ones :)
If you ask me SFML would get alot cleaner, much more professional appeal etc if you'd implement exceptions.
-
Excuse my stupidity, but why wont they work well with shared libraries?
It's tricky to get exceptions that can cross shared libraries boundaries. You can read a lot about it on the internet ;)
You wouldn't have to translate them to other languages, just keep them to the most supported ones
I need to report errors in bindings, so I have to translate exceptions to something else that the target language supports ;)
If you ask me SFML would get alot cleaner, much more professional appeal etc if you'd implement exceptions.
I agree :(
-
Wasn't a reason not to implement exceptions that these errors are not considered fatal? That is, when an image can't be loaded, just show a white sprite instead of terminating the application?
Anyway, one can easily encapsulate the SFML functions and provide exceptions on top of them. That's what I do in the Resources module of my library, by the way.
-
Wasn't a reason not to implement exceptions that these errors are not considered fatal? That is, when an image can't be loaded, just show a white sprite instead of terminating the application?
It was a reason. But after realizing that error handling is currently a total mess in SFML, I think that exceptions would make things a lot cleaner and safer.
-
Well I hope it doesn't come as else I will have to remake rbSFML I think.
Though a better way to handle errors is not bad actually.
-
Maybe a way to enable/disable exceptions? So that bindings can still work with the current implementation, and those who want to use exceptions do something like sf::Exception::SetEnabled(true).
-
Maybe a way to enable/disable exceptions?
It's not just a technical detail, it means a complete redesign of functions -- especially removing error codes from return types, and possibly making something useful of them.
-
I wouldn't change anything as for the return codes, not to break current projects and bindings. But just throw exceptions, if enabled, where it should be thrown.
That way, you have exception support without breaking anything.
-
It doesn't make sense to have return codes together with exceptions...
-
A very (very!) long time ago I asked the same question. http://www.sfml-dev.org/forum-fr/viewtopic.php?p=3522&highlight=exception#3522
At that time you convinced me with your arguments; will these arguments convince you today too ? :P
-
No ;)
These arguments are still valid, but there are also many drawbacks that I didn't see at this time.
-
I would suggest following the Boost.Filesystem approach to the dual exception/error-code interface problem.
Pseudo-code example:
namespace sf
{
class ErrorCode
{
// impl..
};
class Exception
{
// impl...
};
//...
void Devil::SetNumber( int param )
{
ErrorCode ec;
SetNumber( param, ec );
if ( ec )
{
throw Exception( ec );
}
}
void Devil::SetNumber( int param, ErrorCode& ec )
{
if ( param != 666 )
{
ec = ErrorCode( EvilError );
return;
}
m_Number = param;
}
} // namespace sf
-
How does that solve the problem of exceptions across shared libraries boudaries? ;)
-
I wouldn't change anything as for the return codes, not to break current projects and bindings.
Remember SFML2 can break it's interface as much as it's needed until released, no restrictions here. Let Laurent change it as much as he feel needed, in the end it's going to be better for all of us :).
We use SFML2 and we always have stuff to change, but it's a price to pay for using a version that isn't stable yet.
Maybe the bindings shouldn't be worked until it comes more stable.
I didn't know about the problems with shared libs, thanks (I'll have to retrace my codex project :( ). So that's a real problem...
-
As far as I know, the shared library problem arises mainly if different runtime libraries are linked together. But this is generally a bad idea in C++, because it comes with many restrictions (different heaps, duplicate global data, incompatible STL containers). Following this argumentation, SFML might not use std::string in its interfaces either.
If we couldn't use exceptions across shared libraries at all, that language feature would be close to useless. Many C++ libraries use exceptions without problems. But I see that exceptions are problematic for language bindings (already because languages like C don't support them).
-
How does that solve the problem of exceptions across shared libraries boudaries? ;)
As presented it doesn't, and nor should it. You do realise that Boost.Filesystem is a compiled library? The "exception problem" really isn't that much of a problem, and as far as it is a problem, the solution is to let the user compile the library themselves when they need non-default compiler settings (again, just as Boost.Filesystem does).
That being said, you could of course sidestep the issue all together by using the technique I presented, but have the throwing version of the functions be defined, inline, in the header file.
-
As far as I know, the shared library problem arises mainly if different runtime libraries are linked together
It's true.
But this is generally a bad idea in C++, because it comes with many restrictions (different heaps, duplicate global data, incompatible STL containers). Following this argumentation, SFML might not use std::string in its interfaces either.
SFML shouldn't use std::string, yes (that's the reason why debug/release, or vc2008/vc2010 libraries can't be mixed) :P
But I see that exceptions are problematic for language bindings (already because languages like C don't support them).
This is not a blocking point to me, I guess that I can still find a workaround.
Many C++ libraries use exceptions without problems.
Which ones?
I've tried to read as much as possible about this issue, and I could find two relevant things:
- everyone says "don't throw exceptions across libraries boundaries", but nobody really know why
- it should be safe if both libraries and executables are compiled with the same compiler with the same settings -- which is already mandatory with SFML since it uses the STL in its public interface
So I guess it would be worth giving it a try. But I'd hate changing everything and then realizing that it doesn't work well with some OS/compiler. Any idea? :D
-
Which ones?
For example Boost, Poco, Ogre.
This is not a blocking point to me, I guess that I can still find a workaround.
In CSFML, you would have to use return codes or a global variable. I don't know how much you want the usage to differ in SFML and its bindings, but I would not try to emulate exceptions in C ;)
Any idea? :D
Another question: Where in the library would you introduce exceptions?
There are maybe really some justified use cases for return codes (the "non-fatal cases"). Eventually, some people end up writing wrappers that catch exceptions and return bools :P
-
but I would not try to emulate exceptions in C ;)
You can think even Wikipedia have a C++ inspired example of it with both Try and Catch macros. :P Though it's not C-like so CSFML should not do it. Just wanted provide unnecessary facts ^^
-
There are maybe really some justified use cases for return codes (the "non-fatal cases"). Eventually, some people end up writing wrappers that catch exceptions and return bools :P
Or you just use the dual exception/error-code technique presented earlier in this thread and everyone is happy, including binding authors. Note also that this technique is going to become standard practice in the next C++ version (using the already available std::error_code et al).
-
Though it's not C-like so CSFML should not do it.
The problem is not only that exceptions don't exist as a language feature in C, rather that they don't fit the entire language paradigms. In C++, we have RAII to easily write exception-safe code. This is also an issue to consider in managed languages like C#, where no real RAII (apart from using and finally) exists.
By the way, I don't know how well exceptions work across threads.
Or you just use the dual exception/error-code technique presented earlier in this thread and everyone is happy, including binding authors.
Attempts to make everyone happy are the keystone of every bad design. I don't consider the duality a good practice to apply in general. An exception doesn't exist if it can be replaced by a bool flag in every case.
Just consider a constructor that might fail. An exception is a good way to abort it. On the other side, providing an additional error checking mechanism implies the existence of a zombie object state. We introduce again the disadvantages we wanted to avoid by using exceptions.
Note also that this technique is going to become standard practice in the next C++ version.
Can you explain this?
-
By the way, I don't know how well exceptions work across threads.
They don't :P Exceptions thrown inside a thread can only be caught by that thread. As a curiosity, on codex I catch and store any exception from the stream threads and re-throw it later to the main thread.
-
Attempts to make everyone happy are the keystone of every bad design. I don't consider the duality a good practice to apply in general. An exception doesn't exist if it can be replaced by a bool flag in every case.
Just consider a constructor that might fail. An exception is a good way to abort it. On the other side, providing an additional error checking mechanism implies the existence of a zombie object state. We introduce again the disadvantages we wanted to avoid by using exceptions.
I don't actually disagree with that, but there is a problem here, and Boost.Filesystem has a reasonable answer, or are you also suggesting that Boost.Filesystem is badly designed?
Note also that this technique is going to become standard practice in the next C++ version.
Can you explain this?
Sure.
The error handling facilities from Boost.System, used in both Boost.Filesystem and Boost.Asio to provide the exact interface we are discussing, have been incorporated into the C++11 FDIS. Both Boost.Filesystem and Boost.Asio are expected to the incorporated into the next C++ standard. Once they become standard, I would also expect to see more libraries use the same, already standardised, error handling facilities (i.e. both exceptions and error-codes).
-
I don't actually disagree with that, but there is a problem here, and Boost.Filesystem has a reasonable answer, or are you also suggesting that Boost.Filesystem is badly designed?
I haven't worked with the error codes in Boost.Filesystem yet, so I can't really judge it. How do they solve the problem of failing constructors? Anyway, I wouldn't take good design for granted just because it is Boost ;)
Also take into account that SFML has other aims and another target group than Boost, for example it tries to be beginner-friendly and to have a clean API without much redundance, while Boost libraries often intend to be as generic and configurable as possible.
By the way, thanks for the explanation, I didn't know about the integration of error codes into C++11.
-
I haven't worked with the error codes in Boost.Filesystem yet, so I can't really judge it. How do they solve the problem of failing constructors?.
I just had a look through its source, and in fact they don't. For some reason the documentation doesn't have an exception specification on the 'path' constructor, but it can throw if character conversion fails.
Just to reiterate, I don't disagree that exceptions are the One True Way to report errors from constructors, except when they're not: a type that has a legitimate intermediate state, or where failure is expected to occur during normal execution. Examples would include std::iostream construction, where opening the file may fail, or std::shared_ptr being constructed from a null pointer.
Also take into account that SFML has other aims and another target group than Boost, for example it tries to be beginner-friendly and to have a clean API without much redundance, while Boost libraries often intend to be as generic and configurable as possible.
Error codes are hardly rocket science, and they will have to learn them sooner or later anyway, since they are soon to be standardised.
By the way, thanks for the explanation, I didn't know about the integration of error codes into C++11.
You're quite welcome.
And in all this, nobody thought to mention that in fact SFML already uses exceptions. sf::Shape::AddPoint, for instance, will throw an std::bad_alloc exception on memory exhaustion.
-
I've read a lot about the next C++ standard and yet I've never seen anything about error codes. Do you know any good article about this?
-
Unfortunately not. There was a series written by one of the library's authors, but it seems his site is currently down.
There are various links to documentation available, though:
VC2010: http://msdn.microsoft.com/en-us/library/ee372183.aspx
Boost.System (the library's original seed): http://www.boost.org/doc/libs/1_46_1/libs/system/doc/index.html
-
Thanks.
-
For SFSL, I was strongly considering taking the WinAPI approach to error handling. Keep the simplistic true/false return, but provide more details about the error if needed via a GetLastError/SetLastError mechanism.
The last error could be stored in a sf::ThreadLocalPtr making it threadsafe.
Adding the possibility of throwing on failure is relatively easy with this setup, as well. You could allow the user to specify boolean values to indicate whether or not they want it to throw on specific errors. Then in SetLastError, if you set such an error, just throw it.
EDIT:
Another possibility is deriving classes from a common "Errorer" class which has its own Get/Set error functions.
-
As a reminder: Reworking the error handling mechanism is better done before than after the release of SFML 2.
The problem with exceptions thrown across shared library boundaries, seems (according to the quick googling I just did ;))not to be a showstopper, as long as (a) SFML is not loaded with LoadLibrary or dlopen and (b) application and library are compiled/linked with the same compiler with the same version, which must be done anyway.
As Nexus mentioned (http://www.sfml-dev.org/forum/viewtopic.php?p=34713#34713) it works well at least in Boost and other big libraries. The Boost Filesystem (http://www.boost.org/doc/libs/release/libs/filesystem/v3/doc/reference.html#Error-reporting) library, which is among the few boost libraries that need to be compiled (also as a shared library!) for example, makes extensive use of exceptions.
But if you absolutely do not want to use exceptions and keep the return codes, a GetLastError/SetLastError mechanism, like Disch proposed would still be a big improvement over the current stream-based system, which makes it very difficult to log the error messages to a file in a customized format (e.g. HTML), display them to the user (in a non-console application), translate them to exceptions yourself, or generally do anything useful with them.
Maybe you could also call a user defined callback, or raise a signal (http://www.cplusplus.com/reference/clibrary/csignal/).
-
It's definitely too late to switch to exceptions, SFML 2 is almost released. Let's discuss it again for SFML 3 ;)
I can add later a function to get the last error, since it doesn't break the public API.
-
But still wasn't exception a "showstopper" for bindings since not all bindings support exceptions or are very incompatible with C++ exception code?
I am not against nor for exception, I like to see them be used where they fit which isn't as simple as you might think.
In the ruby bindings we use the stream based system to either raise exceptions or keep the original behaviour based on a configuration flag in the SFML module. Don't know but is something like that possible? Or might be stupid to actually have two different behaviours?
-
But still wasn't exception a "showstopper" for bindings since not all bindings support exceptions or are very incompatible with C++ exception code?
Yep, I still don't know how to translate them to C.
-
Yep, I still don't know how to translate them to C.
Just catch them and return error codes? e.g
try
{
texture->This->LoadFromFile(filename, rect);
}
catch (Exception& e)
{
//cleanup
SetLastError(e.what().c_str());
return NULL;
}
The C binding itself is in C++, so it can handle exceptions.
-
Well why not just have C-style error checking in SFML in 2.0 and then do exceptions for version 3? You could use a Uint16 and have plenty of error codes to return, and then have an array of std::strings or const char * strings in SFML_system.h and just return those with a getError function that just spits that back when the user calls it? My personal motivation is that I haven't been able to run any SFML 1.6 code on either of my computers for awhile (with MingW, MSVC++ is fine), even though I updated my graphics drivers and used mingw-with-gcc-4.4. You could use the RenderWindow constructor to just set the values and then use an initter to actually make the attempt and then return an error code, that's how I see it.
-
In SFML 3, the C++11 standard library with its std::error_code could also serve as inspiration for an error-handling design.
-
By the way, I don't know how well exceptions work across threads.
std and boost thread both catch any exceptions thrown in the thread, and then rethrow them when a join() is called. That's how they pass them to different threads. If it's not done by the code explicitly like that, I think it's undefined. It could just vanish into the abyss and the thread ends, or it could die and take the whole program with it. I'd hope it does the latter, but fear it'd do the former.
I don't know if SFML does something similar with sf::Thread. I try to use std::thread or boost::thread if that's not available (like on windows) with wrappers around boosts differences to bring them in line with the standard.
Personally I've always felt exceptions were for exceptional circumstances in C++, places where you very much want the whole program to be potentially aware of a critical error and all pre-tense of encapsulation to be broken. Places where the best thing for your program is probably to just kill itself as quickly and messily as possible. So for me they've always been for if things go wrong at a fundamental level, like running out of heap memory. Otherwise, avoid.
This has the downside of not being able to identify specific problems when multiple things can go wrong, but the afore-mentioned override method could work for that if you care (of course in an ideal world, each function would only have one possible error xD). Or maybe instead of a reference, take a pointer that defaults to null. If it's null, just fail. If not, store specifics in that which it points to. Perhaps uglier, but less writing at least xD
Just my 2 pence.
-
The C++ 2011 standard provides std::exception_ptr, see this MSDN page (http://msdn.microsoft.com/en-us/library/dd293602.aspx) for example.
-
The C++ 2011 standard provides std::exception_ptr, see this MSDN page (http://msdn.microsoft.com/en-us/library/dd293602.aspx) for example.
That's it! I was trying to think of the thing they added. I'm correct in that the join point is specifically when std::thread rethrows, yes? As I understand it still has to be done explicitly (as std::thread does), but it does at least make it easier...