Canceling a Constructor [C++]

Started by
17 comments, last by vreality 12 years, 7 months ago
Thanks :wink:

I think that loading textures has lots of reasons to fail (file not found, memory problem, etc.. ) That's why I proposed to decompose the 'not failing code ' with the 'potentially failing code', (not only for performance purpose)
Advertisement

I think handling exceptions in C++ is more expensive than simply check a boolean value .... (?)


Yes, it takes about 4 cycles to push the exception handler on stack.
Then, 20 billion cycles are spent loading the tga file.

Booleans are much faster, they take 2 cycles to check.

Also, since object is allocated on heap, there is a mandatory 200 cycle cache miss penalty between new and constructor call.


Now we have a more important question. If loading fails, what then? Do we exit, in which case performance doesn't matter? Since such application fails fast, the loading phase becomes serial, meaning there is no productive work we can do until everything is loaded so optimizations don't apply.

Or do we continue. If we continue, then checking for error is redundant, whether by exception or flag - we'll continue anyway. If anything, we might want to log the failure to load desired texture. The actual class instance might then use the common practice of defaulting to built-in error texture, such as red square with ERROR printed on it.

I think that loading textures has lots of reason to fail (file not found, memory problem, etc.. )[/quote]
Run-time part typically has no failure modes. There is exactly nothing end user can do if any of these are a problem.

Either the desired texture is loaded or stub is used. An engine which displays occasional wrong texture is better than one that doesn't run. Whether it's memory, missing file, corrupt data - the spice must flow...

Tools might behave differently, reporting a missing texture, but that would be considered a hard error. Either way, editor must continue not only to be able to open the file, but also preserve missing resources.
Antheus, you always have great responses. +1

You bring up a good point in that if a texture fails to load, a substitute texture should be used instead. There are many different reasons why a texture load could fail, and each one should be handled differently. I believe it depends on where the texture loading is occurring that will determine how to handle each of these cases. Therefore, it is probably best to have some sort of general function which loads textures, and deals with these potential problems in a central area. So, for example, instead of a call to LoadTGA texture, a function should be created called

MyTexture* LoadTexture(std::string filename);

The function LoadTexture should analyze the filename passed, do a filecheck to see if it exists, then pick which loader to use based on the extension passed. And, if any error occurs, either return a substitute texture, or return NULL.

This will abstract away the nitty gritty, often redundant code of creating textures to a central area, making it easier to deal with any errors down the road.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
Seems to me that what you would do in error cases doesn't matter here ? (What the difference in error handling between 'boolean' management and 'exception' management ? You have an error while loading your texture that's it, what you do with it is another problem...)
You should know and understand what a class invariant is. Why you should have them, what impact they have on your code design.

Having class invariants is one of the fundamental concepts behind object-oriented programming. It's up there with "turn a screw one way it tightens, the other way it loosens" in mechanical engineering. It's just that fundamental.

BTW, you should also read the links at the end of the Wikipedia article.

Stephen M. Webb
Professional Free Software Developer

What I do is use a generic Texture class to represent image data, then use helper classes to load them from files. For example:




class Texture
{
private:

//Your texture data stored in here

public:

//Resets member data
Texture(void);

//Copy constructor to handle deep copy of allocated internal data
Texture(const Texture &Tex);

//Assignment operator to destroy and handle deep copy of allocated internal data
Texture &operator = (const Texture &Tex);

//Destroy allocated data
~Texture(void);

public:

/*
List methods here that:
- Creates texture data according to external parameters (res, pixel format, etc)
- Destroys texture data manually
- Gets parameters, such as attributes and buffer pointers, etc
*/
};


class TGA
{
public:
bool Load(Texture &Tex, const std::string &Path);
bool Save(const Texture &Tex, const std::string &Path);
};

class PNG
{
public:
bool Load(Texture &Tex, const std::string &Path);
bool Save(const Texture &Tex, const std::string &Path);
};

class JPEG
{
public:
bool Load(Texture &Tex, const std::string &Path);
bool Save(const Texture &Tex, const std::string &Path);
};


//Somewhere else in code...
Texture Tex[3];

TGA Tga;
if (!Tga.Load(Tex[0], "foo.tga")) {/* error */}

PNG Png;
if (!Png.Load(Tex[1], "foo.png")) {/* error */}

JPEG Jpeg;
if (!Jpeg.Load(Tex[3], "foo.jpeg")) {/* error */}



...something like that.
Latest project: Sideways Racing on the iPad

Vincent,

Allocating memory from the heap is a messy expensive process. C++ responds to this by never using the heap unless you explicitly tell it to (by calling "new").

The vast majority of objects exist on the stack. That is, when you call a function, the stack pointer jumps by enough to allow space for every object local to that function. That way it doesn't need to muck around with allocating memory for them, and when the function returns, the pointer jumps back to where it was before the function call and the program "forgets" about the objects that were in that space (which then gets reused by the next function call). Efficient, simple, and no heap allocations to clean up.

The implication though, is that the constructor actually has nothing to do with the allocation of memory for holding the object itself. The actual memory that holds the object might have been allocated by "new", or might be on the stack. The constructor doesn't know. By the time the constructor is run, the object (though yet to be initialized) already exists, so the constructor can't decide not to make one. That's why it doesn't return a pointer ("new" does), and can't return "null".

The basic rule of dynamic memory usage is "if you create it, you're responsible for destroying it", which leads to the generally true, "If you didn't create it, you're not allowed to destroy it. Hell you don't even know if it can be destroyed." Which leads us to, "Since the constructor didn't allocate the memory for the object, the constructor can't deallocate it".

Now programs do routinely pass responsibility for dynamically allocated objects on to others. But in order to allow the object's constructor to assume responsibility for the object's own heap storage, it must only ever be called for a dynamically allocated object. In other words you must somehow never allow one to exist on the stack. And the constructor must then somehow pass the responsibility on to something else if it decided not to destroy itself.

Nitpick:

//Resets member data
Texture(void);

//Destroy allocated data
~Texture(void);


(void) is an old C throwback. In C++ we just use empty parenthesis. Clearly constructors and destructors don't exist in C, so it's not like this code can be compiled by a C compiler anyway.


My 2c:
The case of a texture not being found is most likely an exceptional circumstance. As such, exceptions are suitable. I would free yourself from the burden of having to check the result of a returned boolean in several places (yes "expensive" can apply to coding time as well as performance) and just put a single try catch block in one place to catch all such exceptions from missing textures.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

Anyway, +1 to the "Factory". One way to structure it to get the behavior you seem to be after might be something like:


struct TGATextureData;

class TGATexture
{
  private: // Objects of this class can *only* be created by members of this class.
    TGATexture(); // Unimplemented.
    TGATexture(TGATextureData *); // The factory uses this one.

  public: // This factory is the only way to get a TGATexture object.
    static TGATexture * GetTexture(char const * filename);
    ...
};
...

TGATexture * TGATexture::GetTexture(char const * filename)
{
    TGATextureData * pTextureData = LoadTGATextureData(filename);
    if(pTextureData == nullptr)
    	return nullptr;

    return new TGATexture(pTextureData);
}
...

TGATexture * pTexture = TGATexture::GetTexture(filename);
if(pTexture != nullptr)
{ ... }

Where LoadTGATextureData() is a static helper function which can try and fail to load the given file.

This topic is closed to new replies.

Advertisement