You've pretty much got it right, a concrete type is something that you can create an instance of, while an abstract type is not. For example, consider a typical pedagogical hierarchy such as:
class animal {
virtual void noise() = 0;
}
class dog: public animal {
void noise() { cout << "bark\n"; }
public:
human *master;
}
class cat: public animal {
void noise() { cout << "meow\n"; }
public:
std::vector<human *> slaves;
}
You can create instances of dog
or cat
because they are concrete, and not abstract. On the other hand, you can't create an instance of an animal
, but only hold a pointer (or refernce) to one. For example:
void hungry(animal *a) {
a->noise();
}
In terms that Stroustrup is using, the representation of an animal
is not included in the definition of class animal
. The representation for a particular kind of animal is described in a subclass, here either dog
or cat
.
First, I feel obliged to point out that std::exception
and its children were designed a long time ago. There are a number of parts that would probably (almost certainly) be different if they were being designed today.
Don't get me wrong: there are parts of the design that have worked out pretty well, and are pretty good examples of how to design an exception hierarchy for C++ (e.g., the fact that, unlike most other classes, they all share a common root).
Looking specifically at logic_error
, we have a bit of a conundrum. On one hand, if you have any reasonable choice in the matter, the advice you quoted is right: it's generally best to fail as fast and noisily as possible so it can be debugged and corrected.
For better or worse, however, it's hard to define the standard library around what you should generally do. If it defined these to exit the program (e.g., calling abort()
) when given incorrect input, that would be what always happened for that circumstance--and there are actually quite a few circumstances under which this probably isn't really the right thing to do, at least in deployed code.
That would apply in code with (at least soft) real-time requirements, and minimal penalty for an incorrect output. For example, consider a chat program. If it's decoding some voice data, and gets some incorrect input, chances are a user will be a lot happier to live with a millisecond of static in the output than a program that just shuts down completely. Likewise when doing video playback, it may be more acceptable to live with producing the wrong values for some pixels for a frame or two than have the program summarily exit because the input stream got corrupted.
As for whether to use exceptions to report certain types of errors: you're right--the same operation might qualify as an exception or not, depending on how it's being used.
On the other hand, you're also wrong--using the standard library doesn't (necessarily) force that decision on you. In the case of opening a file, you'd normally be using an iostream. Iostreams aren't exactly the latest and greatest design either, but in this case they get things right: they let you set an error mode, so you can control whether failing to open a file with result in an exception being thrown or not. So, if you have a file that's really necessary for your application, and failing to open it means you have to take some serious remedial action, then you can have it throw an exception if it fails to open that file. For most files, that you'll try to open, if they don't exist or aren't accessible, they'll just fail (this is the default).
As for how you decide: I don't think there is an easy answer. For better or worse, "exceptional circumstances" isn't always easy to measure. While there are certainly cases that are easy to decide must be [un]exceptional, there are (and probably always will be) cases where it's open to question, or requires knowledge of context that's outside the domain of the function at hand. For cases like that, it may at least be worth considering a design roughly similar to this part of iostreams, where the user can decide whether failure results in an exception being thrown or not. Alternatively, it's entirely possible to have two separate sets of functions (or classes, etc.), one of which will throw exceptions to indicate failure, the other of which uses other means. If you go that route, chances are pretty good that one should be a wrapper around the other, or both act as wrappers around a shared set of functions that implement the real guts of the work.
Best Answer
Most of those bigger C++ libraries were started before
std::string
was standardized. Others include additional features that were standardized late, or still not standardized, such as support for UTF-8 and conversion between encodings.If those libraries were implemented today, they would probably choose to write functions and iterators that operate on
std::string
instances.