官术网_书友最值得收藏!

Error handling

The basic steps are done, the main functionality is implemented. However, there may be errors which we have to recognize and handle meaningfully. The first error can occur during the loading of the texture. For example, the specified file might not exist, or the file might have an invalid image format, or be too big for the video memory of the graphics card. To handle such errors, the method sf::Texture::loadFromFile() returns a Boolean value that is true in case of success, and false in case of failure.

There are several strategies to react to resource loading errors. In our case, we have to consider that the texture is later needed by sprites that are rendered on the screen—if such a sprite requests the texture, we must give something back. One possibility would be to provide a default texture (for example, plain white), so the sprites are just drawn as a white rectangle. However, we do not want the player of our game to fiddle around with rectangles; he should either have a proper airplane or nothing. But how can we implement "nothing"? We have to notify the caller of our load() method that something did not work. A possibility to implement these notifications is shown in the next sections.

Boolean return values

We could follow SFML's philosophy and return a Boolean value denoting success or failure. This approach has some disadvantages. We cannot use the return type for something else. Additionally, the caller has to check the returned value every time he calls load(). This is easily overlooked, and if it is not, it leads to messy usage code that is full of error checks. That is not what we want, as initially stated our objective consists of performing as much work as possible in the TextureHolder, to relieve the user from writing boilerplate code.

Throwing exceptions

Another approach to react to a loading failure is to throw an exception. We choose the standard exception type std::runtime_error. To its constructor, we pass an error message describing the problem as clearly as possible, including the filename:

if (!texture->loadFromFile(filename))
    throw std::runtime_error("TextureHolder::load - Failed to load " + filename);

Exceptions have the big advantage that user code can be kept clean of error handling. Clients can now have the following code:

TextureHolder textures;
textures.load(Textures::Landscape, "Media/Textures/Desert.png");
textures.load(Textures::Airplane, "Media/Textures/Airplane.png");
textures.load(Textures::Missile, "Media/Textures/Missile.png");

We do not need to check every single call. If an error occurs, an exception will be thrown until a try-catch block catches it and reacts meaningfully. It is possible that the exception passes several functions before it is eventually handled.

Once the resource is loaded, we insert it into the map. Here, we have to be aware of possible error sources too. When the given ID is already stored, the map will refuse to insert our ID-resource pair, as it cannot contain duplicate keys. The member function std::map::insert() returns a pair with an iterator to the inserted element and a Boolean value which is true if inserting was successful. We store this returned pair and check its second member (the Boolean value). Instead of writing std::pair<std::map<Textures::ID, std::unique_ptr<sf::Texture>>::iterator, bool>, we can use C++11 type inference:

auto inserted = mTextureMap.insert(std::make_pair(id, std::move(resource)));

Now, inserted is our pair containing an iterator and a Boolean value, inserted.second is the Boolean value denoting the success of the insertion. If it is false, we know that the ID is already stored in the map. How do we react to this situation?

We could throw a std::runtime_error exception again. However, in contrast to a loading failure, double insertion is not a runtime error. The attempt to insert the same ID twice in the map is a logic error, meaning, there is a mistake in the application logic—in other words, a bug. A well-formed program would not attempt to load the same resource twice. In comparison, runtime errors occur in correctly written programs too, for example, if the user renames or moves the resource files. For logic errors, the standard library provides the exception class std::logic_error.

This raises already the next question: how do you handle such exceptions? It is not that once you have thrown an exception, you can forget about it and the world is in harmony. Somebody has to catch those exceptions, and it had better be you and not the operating system (unless you like crashing applications). In the case of a loading failure, we can tell the player that the files were not found, and prevent him from starting the game. But what in our double insertion case? Are we supposed to tell the player that the programmer accidentally called load() twice? Certainly not. This bug must not occur in the final application. There is no way to recover from it—continuing the application is dangerous, because its logic is broken, and we risk upsetting even more if we ignore the error. What if the two load() calls are passed the same ID, but different filenames? We do not know with which resource the ID is associated. If we later want to access a resource by its ID, we might get the wrong resource, and thus display a wrong image on the screen. In this manner, errors can propagate further and further, sometimes remaining for a long time before being noticed. In case of a logic error, we would like the program to interrupt immediately.

Assertions

Clearly, a mechanism apart from exceptions is appropriate, which shows us directly and inevitably when something goes wrong. This is where assertions come into play. The macro assert evaluates its expression; if it is false in debug mode, a breakpoint is triggered, halting the program execution and directly pointing to the source of the error. In release mode, assertions are optimized away, so we do not waste any performance to check for errors that cannot occur. The assert expression is completely removed in release mode, so make sure you only use it for error checks, and not to implement actual functionality with possible side effects.

We have to insert a single line, we expect that the Boolean member of the pair returned by std::map::insert() is true:

assert(inserted.second);

That is already it. The whole method looks now as follows:

void TextureHolder::load(Textures::ID id, const std::string& filename)
{
    std::unique_ptr<sf::Texture> texture(new sf::Texture());
    if (!texture->loadFromFile(filename))
        throw std::runtime_error("TextureHolder::load - Failed to load "     + filename);

    auto inserted = mTextureMap.insert(std::make_pair(id,   std::move(texture)));
    assert(inserted.second);
}

In our method get(), there are things that may go wrong too. The requested texture might not exist in the map. This is also a logic error; we are supposed to load the textures before we access them. Consequently, we verify whether the texture has been found, again using assert:

sf::Texture& TextureHolder::get(Textures::ID id)
{
    auto found = mTextureMap.find(id);
    assert(found != mTextureMap.end());

    return *found->second;
}
主站蜘蛛池模板: 城步| 夏河县| 海城市| 庆城县| 静乐县| 故城县| 江山市| 乌海市| 十堰市| 延吉市| 三门峡市| 英吉沙县| 滁州市| 施秉县| 罗城| 博湖县| 新津县| 浮梁县| 旅游| 望江县| 昌黎县| 慈利县| 绥中县| 尼勒克县| 开封县| 舟曲县| 定远县| 彭山县| 米泉市| 开鲁县| 黄梅县| 沽源县| 磐石市| 开远市| 聂拉木县| 元谋县| 呼玛县| 志丹县| 棋牌| 阳山县| 简阳市|