Kylotan: You are confusing the amount of variables with subjectivity. You can have 1000 variables in an algebra problem, but it''s still logic that solves it. You could argue that there are many different ways to solve that problem; just the amount of variables tells you that. However, there will only ever be one best way to solve it (the way requiring less work and thus less chance for error is the best way, whatever it may be in this problem). Programming is one complex solution made out of many simple solutions, just like that algebra problem.
Just because something is rated on a scale does not mean it is subjective; if that was true we wouldn''t have measurements.
quote:
Original post by Kylotan
quote:
--------------------------------------------------------------------------------
Original post by null_pointer
Kylotan: If you are using exceptions in the way that they were intended to be used, then you are correct in saying that there is not a better way. If you are using them against their definition, then there is OBVIOUSLY a better method somewhere else.
--------------------------------------------------------------------------------
Surely that assumes the language is perfect and has a perfect tool for every task? I don''t believe it is perfect, and therefore it is possible to find solutions which go against the standard definition without there necessarily being a better way of doing it.
Hmm...no, but you are counting on the programmer being perfect, too, because he must compare the language to his own logic to tell which is better. In other words, the moment you start to evaluate the language, you make it subjective. You are subjectifying it. (yes, I am holding a dictionary
)
I would say from experience that 9 out of 10 problems, bugs, headaches, etc. wouldn''t exist if programmers would just use the language the way it was intended. The only thing you have provided with exceptions was a workaround for type-checking. I will go over exactly what it accomplishes and what it does not -- the pros and the cons -- as well as ease-of-use. We will see which method is better, objectively.
Your method
~~~~~~~~~~~
Using one global class to hold all types of exception information. Using strings to hold the type of the class, and also the description of the exception.
Pros:
1) Allows user to handle all exceptions with just one catch statement.
Cons:
1) catch() statement type-checking can no longer be used.
2) User must catch and handle all errors at the same place.
3) Even though he has caught a generic exception, he must still switch on the type, which is no better than the type-checking that was eliminated.
4) The syntax checking of the compiler was disabled. You cannot easily check for wrong exception names, and the code may or may not run as planned.
Example:
void MyApp::LoadMRUFile(int nPosition)
{
if( nPosition > nMaxPosition )
throw exception("MyApp::LoadMRUFile", "nPosition was too large");
// save the current document
document old_document(current_document);
// continue loading
try
{
file mru_file(GetMRUFileName(nPosition));
mru_file.open();
mru_file.read((void*) ¤t_document, sizeof(Document));
mru_file.close();
// make sure it''s valid
if( !current_document.check() )
throw exception("MyApp::LoadMRUFile", "document loaded from disk was invalid");
} // end try
catch(exception e)
{
// we still need to switch type, because one of the
// functions we''ve called might have thrown another
// type of exception
switch(e.type)
{
case "MyApp::LoadMRUFile":
{
// do something -- possibly restore old_document
if( old_document != current_document )
current_document = old_document;
// re-throw the error, or else user will have
// no clue as to what happened
throw;
} break;
}
} // end catch
}
Explanation:
When using that particular "method" of exception-handling, you must always switch on the type to make sure you are handling local exceptions. If you wish to handle non-local exceptions (i.e., from functions you have called), then you must depend on what code will call which exceptions. Unfortunately, you have destroyed the throw() syntax that comes after the function declaration, so you have no way of knowing what functions throw what exceptions without looking at the source for the class. The user of your class has to go through your source, too. Also, you place the exception types in the .cpp files, mixed among the code, when they could be in the .h files at the beginning of the class. This method has nothing to do with modularity, as it creates dependencies all across the board.
Note also the disorganized mess in the lone catch statement: is it worth having only one catch handler when the catch block contains all the code of the smaller catch blocks? You are not eliminating code by using one catch statement; you are just combining small catch statements, and accomplishing the same thing.
Worse yet, what if you use the same description in a different place, but don''t spell it correctly? Or perhaps the wording changes slightly? You could get around this with const char[]''s defined in the header, but that''s just working around the language again.
In short, it accomplishes nothing desirable that my method does not accomplish, and imposes extra limitations (which are unnecessary). It also imposes extra overhead.
You will find that every time you work around a language feature.
My Method
~~~~~~~~~
Use one global class, and derive all class exceptions from it. Every class will have a generic exception type, from which the class-specific exception types are derived. Uses empty derived classes as simple type-checking.
Pros:
1) Uses the languages type-checking to determine the type of the exception.
2) Allows user to handle all exceptions with just one catch statement, if desired.
3) Allows user to handle class-specific or all class generic exceptions, or any mixture of both types, if desired.
Cons:
Example:
// classes that are declared in the headers:
// ::exception
//
// file::exception - derived from ::exception
// file::end_of_file - derived from file::exception
//
// MyApp::exception - derived from ::exception
// MyApp::invalid_mru_position - derived from MyApp::exception
//
// document::exception - derived from ::exception
// document::invalid - derived from document::exception
// preceding stuff wouldn''t be duplicated with each function
// it''s just here for reference.
void MyApp::LoadMRUFile(int nPosition)
{
if( nPosition > nMaxPosition )
throw MyApp::invalid_mru_position;
// save the current document
document old_document(current_document);
try
{
file mru_file(GetMRUFileName(nPosition));
mru_file.open();
mru_file.read((void*) ¤t_document, sizeof(document));
mru_file.close();
// make sure it''s valid -- will throw document::invalid
// if not valid.
current_document.check();
} // end try
catch(document::invalid)
{
// restore old document
if( current_document != old_document )
current_document = old_document;
// re-throw, possibly tell the user of the corrupted file
throw; // (goes to catch(::exception) before leaving the function)
} // end catch(document::invalid)
catch(::exception)
{
// we don''t know what happened and we didn''t plan it,
// but we do have a chance to clean up if we need it.
// re-throw the error
throw;
} // end catch(::exception)
}
Explanation:
With this type, we let the compiler juggle the "type strings" and the "description strings," as type-checking was the way exceptions were made to work. Syntax errors are caught by the compiler, and all exceptions are declared in their respective headers. Later on, when it comes time to add the throw(/* exception-type */) to the function declaration (whenever VC 7 comes out!), the user now knows exactly which exceptions can occur in any given function (and also any functions called by the given function).
I am using multiple catch() statements, because each requires different handling. If you find you are duplicating code, re-throw the exception and place a generic handler as shown in the above example to handle the cleanup code. The cleanup code is the same for each exception in the given function, because each function cleans up after itself. Exception-handling does not need to depend on how far we got in the function when the exception was raised.
This is more akin to modularity, at least much more than your string-exceptions.
On a side note (and not to be confused with the main point), because I am writing a library, I have a set of classes that are made to function with each other. If you really wanted a correct model of the classes (file, document, MyApp), you would have made subobjects where dependencies were evident, but I didn''t include them to keep from distracting you from the main point. Further, you can derive from the ::exception class if you want to make ::out_of_memory or whatever you like, and it won''t conflict with the existing code in the least. I am considering adding both types (class-specific and also ::out_of_memory, etc.) to my library.
My method keeps all the pros of the language, and none of the cons (there just aren''t any).
On general principle, I have learned that a sure-fire check to see whether you are using the language incorrectly is if you are: 1) duplicating code, 2) losing type-checking. 1 and 2 seem to occur in every piece of bad code (bad code, meaning: not using the language correctly) that I have examined.
Back to the point, though. You now understand how my method works. To further explain it, the only time RTTI is logically necessary is in the catch(::exception) in main(), which only overrides the "unhandled exception error" to provide more reasonable output for my library. Since everything starts from main(), everything must go back to main(), and that is an excellent place to catch-all.
quote:
Original post by Kylotan
Surely that assumes the language is perfect and has a perfect tool for every task? I don''t believe it is perfect, and therefore it is possible to find solutions which go against the standard definition without there necessarily being a better way of doing it.
No, not necessarily. Far more often the programmer is causing the trouble.
The language is extremely simple to use, if you know it. Most programmers just don''t know how to use it correctly. They waste unheard-of amounts of time working around language features, then posting to message boards because they can''t figure out what went wrong. And here''s the worst part: they don''t realize they dug the hole! In other words, the time they spend working around language features could have been spent learning how to use them, with a lot of time to spare towards solving the current problem.
In other words, I would estimate that: the case of not using the language correctly exists 99% of the time, and the case of trying to do something not covered in the language exists 1% or less of the time. Although many cases appear to be of the second type, they are really of the first type through the ignorance of the programmer.
I absolutely detest the "different methods are equally good" way of thinking. If there exists no feature that does what you want, then make your own. However, it is FAR MORE COMMON that what you wish to do is incorrect. The language is far from being a generic, hulking beast that you must trim down and then "season to taste." There can only be one best solution to any problem, because the word "best" means one. You can have two "good" solutions. But you can NEVER have two "best" solutions. You must decide which you will believe: 1) a "best" solution exists for every problem, or 2) several "good" solutions exists, but not a "best" solution, because of individual differences. 2 is asinine considering the amount of relevant circumstances for every individual problem, and also the obviously imperfect nature of human thought. If you are going to use the word "best" then use it correctly.
Perhaps you are thinking I meant: "there is one best way to solve every problem"? This is what I truly mean: "For each problem, there exists a best way." Understand the difference? We don''t use one way to solve every problem; however each problem has its own best way. Meaning, templates might be the best way to solve one problem, but simultaneously inheritance might be the best way to solve a different problem.
If you are using the language (referring to C++ now) correctly, for a task that it was meant to do, the code will be simpler, cleaner, more efficient, and more vulnerable to optimization than anything you could possibly write yourself. Until the language feature is added or improved, you are only emulating something. Understand? If you want to run 32-bit code on a 16-bit processor, you''re going to have to write an emulator. If you are trying to do something outside the confines of "the box" from inside, you have, at best, a flawed implementation. Flawed, here, means slow, unreadable, illogical, or a combination thereof.
(On a side note, those are all attributes of the same object, bad_code, so they are usually found together.)
What I am saying long-windedly is that while the language is imperfect, it is much closer to being perfect than you think. It is wrong for a programmer to think that the language is imperfect until he has mastered it. Otherwise you will fall into the trap of, in order of severity: 1) bashing the language, 2) working around the language features, and 3) attempting to write your own language "within the box" by trying to introduce things that are outside the box.
I will now deal with the specific cases.
quote:
Original post by Kylotan
quote:
--------------------------------------------------------------------------------
I still don''t understand why any app in a multi-tasking, resource-sharing OS, would "reserve" resources. That makes them unavailable to the OS and to other apps in the mean time. In a multi-tasking OS, you do NOT hog the computer.
--------------------------------------------------------------------------------
In a system where it is possible to allocate X resources to your program, allocating X+1 is not really a big deal.
An example I have worked with: a MUD server can end up terminating for a variety of reasons. In these circumstances, it is imperative that the program leaves a log file to say why it was terminated. If the operating system has no free file handles, then it would be impossible to write this log. Therefore the program takes a file handle when the program starts to ensure that it will have one at the end when it needs it. The correct functionality for the program demands that it has a file handle at the program''s termination, and it therefore should not run unless it can guarantee that state.
quote:
--------------------------------------------------------------------------------
I cannot logically come up with a reason as to why you would need to reserve memory at the beginning of the program so that you could release it when you run out.
--------------------------------------------------------------------------------
Any kind of ''graceful shutdown''. The users of your program may not care so much about your program consuming extra resources, however it may be important to them that it closes down in a stable state. Closing down gracefully may require X kb of memory, and therefore unless you reserve that at the start, you will never be able to shutdown gracefully following an out-of-memory exception.
Well, first, if you are using exceptions when shutting down, your error logging code would be further from the file handle and memory releasing code, and thus the file handles and memory would be released first. That is the correct way to handle the problem. You see, running out of resources was predictable, but the correct way of solving it relied on the nature of exceptions being thrown instead of the work-around of reserving resources or whatever. You over-corrected.
You could argue that the OS might not be in a state where it can juggle file handles, but in that case I doubt it could write information to a log file either. "Reserving resources" has no purpose in a multi-tasking OS, even if it is "just a file handle." Every resource counts, and in an environment where there are typically 100''s of modules loaded they add up fast. Never depend on the user having more resources than you will need during run-time. Also, when shutting down, you will be releasing the resources, then writing to a log file or displaying information to the user. So, logically, you WILL have resources to use for logging at shutdown, providing of course that you have used them before.
Of course, you could say "what if another app took the resources as I was releasing them?" but that is just your bad luck. Suppose the other app needs them just as much, or perhaps more? You cannot know, unless you are intent on taking over the OS and using it as a single-tasking OS like DOS. (I would also like to point out that most OSes have "fail-safe" methods in their APIs for precisely this purpose. Look up the flags that go with MessageBox in Win32 for an example.)
It is the OS''s job to coordinate the resource usage of other apps with your own; it is not your job. Whenever you take that job upon yourself, you take away from the efficiency of that job. You can try to ignore that principle by cutting down the resources you "need" to "reserve," but you will never get rid of the principle. Principles do not change with the varying degrees of their circumstances. Misconceptions do.
quote:
Original post by Kylotan
quote:
--------------------------------------------------------------------------------
If it is predictable and logical that something external may affect your program, you should NOT throw an exception when the operation doesn''t work. It should be checked for validity before the operation.
--------------------------------------------------------------------------------
If I do this:
Type* ptr = new Type;
and there isn''t enough memory, it should throw std::bad_alloc, right? As far as I know, there is no way to check for this lack of memory (something external) before it throws the exception. In this case, it''s predictable and logical that such a thing may happen (computers have finite RAM), but I can''t check it in advance. In effect, the exception is my return value.
Technically, it isn''t your return value, but I understand what you are trying to say there. However, what is your point? It is the job of the OS to make sure that memory is available. It is not your job.
HOW THE LANGUAGE SEES IT
If you look at this as if you were designing the new() operator, this logic is correct. Exceptions enforce correct coding. If the new() operator had not thrown an exception, you would have tried to use a NULL pointer and been met with an odd error that doesn''t tell you the real problem. Since new() throws bad_alloc, you must handle it. Indeed, you cannot proceed along the same program flow path without handling it.
HOW THE OS SEES IT
Why does my logic not make sense here? Where is the conflict? Responsibility is the "hidden" keyword. It is what is meant by "logically expected to handle it." Responsibility, in this case depends on whether the user should see the memory exception and how it should be presented. The OS juggles resources for the entire system, not your app. If the OS needs memory, say, for a new program the user just started up, what can it do? It cannot arbitrarily pick an app and shut it down. It cannot send messages to apps in the system to condense their memory. What must the OS do? It must tell the user to close some programs and try again. It''s the only feasible way.
But what does it do with the programs? Can it pause the program trying to start up until memory is free? What if other apps are calling new and delete too? If the OS paused all apps calling new() when it was out of memory, then it would either lock them up (you wouldn''t be able to close them if they''re paused), or slow down the system tremendously (plus all the virtual memory access time you are probably already using). The memory allocation call should just fail, for the app, and should require the program flow to be changed (now we''re into language territory again -- see above).
If you leave out responsibility, exceptions will look a little lopsided. That is why the phrase "logically expected to handle it" occurs in several forms in many places on this page. Responsibility is not subjective. Thinking that responsibility is subjective is only a method of escaping responsibility.
Let''s look at an example. 4 people are standing on the corners of a square, looking at each other (that is, facing towards the center). In the center of that square is a cube. The cube is painted with 4 colors, and, for each side, only one color is used. Let''s call them red, blue, green, and yellow. To me, because I can see only one side of the cube, the cube looks red. To you, it looks blue. To the other people, it looks either green or yellow. But the cube is not one solid color; it is 4 different colors.
Human perception is like that. We can only see one viewpoint at a time. But the cube exists, and appears different from many angles. Logic can take the different views of the cube and "re-build" the cube from those separate parts. You might say, then that logic is the method by which we converse. However, before you can re-build the cube, you must have a "point of reference." In other words, to tell what the cube looks like, we needed to know the relative positions of the people facing the cube. We need to know how the viewpoints relate to each other.
Perhaps it is wrong to say that "people are illogical." But they do use logic incorrectly. That is why, taking the position and colors of the sides of that cube, it is possible that we each arrive at a different cube. However, the cube really only ever exists in one form. Our perceived cubes are, in fact, only imaginary. If our perceptions of the whole cube are different from each other, then either one or both of the perceptions must be incorrect. (Logical rule: things equal to the same thing are equal to each other; and, also, things not equal to each other cannot all be equal to the same thing.)
The relative would have no purpose without the absolute.
In other words, logic is the method of taking several subjective viewpoints and discovering the objective. If people couldn''t do this, they would never be able to communicate because all things come through a viewpoint before your brain can process them. If you ever wish to evaluate anything in an objective way, you must use logic.
Think about how people play 3D games for a moment. If you simply move towards an object, you don''t have much idea how the room is laid out, but if you move in all directions (sideways, up-down, rotating) you can easily tell the dimensions of everything in the room, and it''s easy to go through it quickly in a death-match. That''s why 3D games with polygons are harder than 3D games with sprites if you don''t know how to move in several ways at the same time. Try limiting yourself to one method of movement and see how it affects your perception.
But you must never make the mistake of believing that your perception is the reality. Reality cannot be viewed objectively, from all angles at once, by a human. That is why logic is so necessary; it allows us to view all angles of reality. Not at once, but we can grasp it by "interpolating" the data from several different perceptions.
I would ask something of you before you reply, though: find me a programming task for which there is no C++ language feature designed. If you have trouble coming up with one, then my point is sound.
If I may, I''d like to ask another question: if there is no absolute for when and how to use exceptions in a given problem, how can they work? Exceptions exist to provide (and enforce) proper communication between programmers'' code. There would be no purpose for exceptions if people couldn''t communicate. There is no communication without a standard.
And another question yet. If you were to say, "I will use the language features as they suit my logic," then you will end up re-writing them to suit your logic (look at the type-checking example above -- bet you had no idea the "built-in" type-checking was that powerful). You must either re-write your logic or the language''s logic, but they cannot function together as-is. Which would you adjust?
So, the debate is not on whether exceptions require a standard but on what the standard should be.
-
null_pointerSabre Multimedia