C++ Zombie flag altneratives

Started by
34 comments, last by SiCrane 14 years, 8 months ago
Quote:Original post by Giblodian
or the method I originally mentioned just seem much simpler than fiddling around with throw,try,catch everywhere.


If you're fiddling around with them, you're probably not using them the way they were "supposed" to be used. throw statements are a replacement for "return ERROR_CODE;" in many situations (though often error codes are still suitable in other places). But try/catch()es should be far less frequent than checking for errors. All in all, it should be significantly less fiddly.

Your example would also have the client fiddle around with manual resource management, too. Not to mention the fact that it could potentially throw a bad_alloc exception (and that does happen on Windows if you're creating a particularly hungry app).

I'm not saying exceptions are a silver bullet. Sometimes, they are a royal PITA. But I would suggest getting to know them better. In some ways (namely stack unwinding), C++ actually has better machinery to handle exceptions than many other languages.
Advertisement
Quote:Original post by Giblodian

I simply do not like exceptions.

Then just do the following:
bool foo(char * filename) {  FILE * f;  f= fopen (filename,"r");  if (f != NULL)  {    processContents(f);    fclose (f);  }}
It's C (possibly with classes), but it works well. It's usually better to stick with POD types, saves a lot of headaches.


Exception-less RAII is possible, just requires discipline. All classes look like this, init() might be virtual:
class Foo {  Foo() {} // empty  bool init(); // actual constructor};
and is used simply as:
Foo foo;if (!foo.init()) return; // or panic in other ways// use foo from here on

The rest works as one would expect. The only difference is that you must make it a habit to do construction in two lines, handling the init()'s result properly. You still get auto clean-up, but lose some of the benefits of true RAII.
Quote:Original post by Zipster
Plus the file class isn't necessarily useless at that point. The object itself should remain in a perfectly valid state so that you can do something like this:
FileClass file("this_file_doesn't_exist.txt");if(!file.is_open())   file.open("this_file_does_exist.txt");
My opinion is that a file object should represent a file, it can open, close, create and delete that file as often as needed. So it is also perfectly valid (and un-exceptional) if the file cannot be opened right away. You shouldn't be able to ask it to open a different file though, that doesn't make sense, there shouldn't even be a mechanism for that (i.e. A filename is taken upon construction and open simply reopens that file for reading and/or writing). IMO such an object is largely useless, you'd never need to create instances of file objects, the only state they keep is the filename and after that they're just manipulating the filesystem using that name.

A file-stream, on the other hand, is a different creature entirely. A single stream can be open or closed, when the stream is open it is directed at a single file source/sink. As a consequence of it not modelling a file, but a stream, it can be made to point to any number of files during its lifetime. If a file won't open then the stream simply remains closed which, as already established, is a common and valid state for it to be in, certainly not an exceptinal one.
Quote:Original post by Zipster
You should really only throw an exception if your code runs into a situation it can't or shouldn't handle. A file class trying to open a file that doesn't exist isn't exceptional behavior, since that's just the nature of file classes and other objects which bind themselves to external resources which may or may not exist, or have other permissions assigned to them preventing you from performing certain actions.
It's possible to argue about the meaning of "exceptional" all day. The historically terrible efficiency of throwing exceptions has led to much quibbling about what is or is not exceptional in C++, as opposed to Java where exceptions are used anywhere and everywhere they come in handy. The fact that no other mechanism exists to indicate a failed constructor in C++, however, suggests that the existence of an object utterly unable to fulfill the postconditions of any of its functions is exceptional regardless of how likely it was to happen. The creators of the IOStreams library decided to go a different route and incorporate the state of the stream into those postconditions, significantly complicating the interface but increasing its flexibility. Whether that was the right route to go is debatable, but the fact is that it was a design choice, not the one true approach. If you want to indicate an error, and you think of it as an actual error rather than just an alternative state of being, throw an exception.
Quote:Original post by Sneftel
The fact that no other mechanism exists to indicate a failed constructor in C++, however, suggests that the existence of an object utterly unable to fulfill the postconditions of any of its functions is exceptional regardless of how likely it was to happen.

I don't disagree. But you're assuming that a) if you failed to open the file in the constructor, the file object itself failed to be constructed, and b) that the the post-conditions of all your functions always assume success and throw exceptions otherwise. As you said yourself these are just design decisions. If I instead defined my post-conditions to include an error state, then I could argue it isn't exceptional for things to fail and the constructor can just set a flag without throwing an exception. The file object is successfully constructed but you have to deal with the error before continuing to use the object.

It all comes down to preference. As you put it, I think a file class should have "alternate states of being" ala fstreams, but that's just me.
Quote:Original post by dmatter
My opinion is that a file object should represent a file, it can open, close, create and delete that file as often as needed. So it is also perfectly valid (and un-exceptional) if the file cannot be opened right away. You shouldn't be able to ask it to open a different file though, that doesn't make sense, there shouldn't even be a mechanism for that (i.e. A filename is taken upon construction and open simply reopens that file for reading and/or writing). IMO such an object is largely useless, you'd never need to create instances of file objects, the only state they keep is the filename and after that they're just manipulating the filesystem using that name.

Why doesn't it make sense? Why is it strictly necessary for a file object to be bound exclusively to a single file[name] for its entirely lifetime? Why is such an object largely useless otherwise?

I think of a file object as something that can be "bound" and "unbound" to different files as often as is wanted, much like you see streams, and not necessarily as an object that actually represents the file itself. Admittedly I rarely use such functionality, but it just seems odd to me that such a mechanism would suddenly cast an otherwise decent file object into the realm of the outrageous and nonsensical.
I feel I have failed to accurately describe my use. This is what I'm doing.
/*DON'T MOCK ME*///Generalized Image classclass Surface{public:        //Load image from disk. Set all the private variables, etc.	Surface(const char *file = "image.bmp"); 	~Surface();         //is called at every method to make sure it isn't a zombie	bool isNull() const; 	int getWidth() const; 	int getHeight() const; 	const SDL_PixelFormat* getFormat() const; 	int getAt(int x, int y); 	bool setAt(int x, int y, RGBA& rgb); protected:	void init(); 	void printNotSet() const; private:	SDL_Surface *Bitmap; 	int Width, Height; 	bool Null; };/*then I have this, which extends the general image class to include operations proprietary to the editor. It is *completely* dependent on an initialized Surface. Note:it's incomplete and planned to do more in the future*/class MapSurface : public Surface {public:	MapSurface(const char *name);	~MapSurface();         //calls isolateObject->traceObject->simplifyObject	void createObject(int x, int y);private:	void isolateObject(int x, int y, int oldColor, int newColor); 	void traceObject(int xStart, int yStart); 	void simplifyObject(double tolerance); 	int **_mapPixels; };

So pretending you're me, only not retarded, what would you do? Exceptions seem the rave and my weird factory thing doesn't appear well received (or even appear to make sense now) so I'm just going to accept that.

My questions are:
Where would you load the file: from the parent or derived class?
from the constructor or prime candidate for an open method?
If loaded from the parent constructor, and an exception is thrown when an error occurs, how would this be utilized/caught? I'm keen on not aborting or exiting, because this is UI driven and it'd be nice to have the opportunity to try opening something else.

is structuring it like this wrong entirely?
Quote:Original post by Giblodian

is structuring it like this wrong entirely?


No, just clumsy.

Look at the example I gave above, which behaves just like exception-based approach would:
class Foo {  Foo() {}  virtual ~Foo();  virtual bool init() { return true; };};


Then, since you cannot rely on exceptions to check construction, you need to do it yourself each time you use an object:
int main() {  Foo foo;  if (!foo.init()) return 1; // foo is broken, we can't continue at all  foo.whatever(); // foo is fine};


For inheritance, there are no problems:
class Baz : public Foo {  Baz() : Foo() {}  virtual bool init() {    if (!Foo::init()) return false;    // derived init  }};


The exceptions equivalent of this would be:
class Foo {  Foo() {    if (!init()) throw exception;  }};


Since constructors work in a different way and have no error reporting abilities, they need to be ignored completely. Whatever the constructor should be doing goes into init(...).

This is sometimes useful anyway, since, unlike constructors, it allows existing instance to be reset. Some smart pointers use such approach, as does auto_ptr.
Quote:Original post by Giblodian
I feel I have failed to accurately describe my use. This is what I'm doing.
*** Source Snippet Removed ***

Your surface class is read-only and includes no non-constructor facilities for loading in data. Given that, there is absolutely nothing meaningful or useful to do with a Surface whose constructor has failed. I think everyone here would agree that that's an exception situation, regardless of what they like their File class to do in error conditions.

Quote:Where would you load the file: from the parent or derived class?
As you've structured it, the parent; after all, the parent needs to still be a Surface, so it still needs to do things that Surface does. MapSurface should expect that of Surface.
Quote:
If loaded from the parent constructor, and an exception is thrown when an error occurs, how would this be utilized/caught?

Wherever you like, further up the call chain. If you have a function that handles the "File->Open..." menu option by putting up a dialog box and then opening the resultant filename, that would be an ideal place to throw up a message box and then return if the constructor throws.

Quote:Original post by Zipster
Why doesn't it make sense? Why is it strictly necessary for a file object to be bound exclusively to a single file[name] for its entirely lifetime?
I only meant that a file class designed to model a file on the filesystem shouldn't be able to bind to different files during its lifetime. Any operations that can be performed on a file instance should mimic operations that you can perform on a file and you can't ask a file to become another file.
For sensible pragmatic reasons it is probably useful to modify the semantics to allow an instance to bind to different files, then that class would model (in my own off-the-cuff parlance) a file-handle.

Quote:Why is such an object largely useless otherwise?
By object I mean an instance of such a class - the same functionality can be just as well be achieved via static/free functions - although I might be wrong about this as I can imagine you might want to keep other state too and the Java File is used via an instance afterall, it does demonstrate the immutability of the filename idea though. Just that in a language where file-streams already exist it seems that the other functionality that they don't provide wouldn't require instances of a file class but could be implemented as static methods of such a class for example.

Quote:I think of a file object as something that can be "bound" and "unbound" to different files as often as is wanted, much like you see streams, and not necessarily as an object that actually represents the file itself.
Yeah, that would be the difference between our notions: a file object that represents a file and a file object that represent a file handle.
(Although I'm not sure I would allow a file handle to be in an unbound state, that adds a lot of complexity to the semantics, but I would allow it to be rebound. I'd do the same for streams if I were to implement my own).

Quote:outrageous and nonsensical.
Haha, perhaps I came across a little strong [grin]

This topic is closed to new replies.

Advertisement