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

An automated approach

Our goal is to encapsulate the just mentioned functionality into a class that relieves us from managing resources again and again. For resource management, the C++ idiom Resource Acquisition Is Initialization (RAII) comes in handy.

Note

RAII describes the principle that resources are acquired in a class' constructor and released in its destructor. Since both constructor and destructor are invoked automatically when the object is created or goes out of scope, there is no need to track resources manually. RAII is mostly used for automatic memory management (as in smart pointers), but it can be applied to any kind of resources. A great advantage of RAII over manual allocation and deallocation (such as new/delete pairs) is that deallocation is guaranteed to take place, even when there are multiple return statements or exceptions in a function. To achieve the same safety with manual memory management, every possible path would have to be protected with a delete operator. As a result, the code becomes quickly unreadable and error-prone.

In our application, we want to take advantage of RAII to determine the construction (loading) and destruction (release) of SFML resource objects.

Let's begin with a class that holds sf::Texture objects and loads them from files. We call it TextureHolder. Once we have implemented the semantics for textures, we can generalize the implementation to work with other resource types.

Finding an appropriate container

First, we must find the right data structure to store the textures. We ought to choose an STL container that does not perform unnecessary copies. std::vector is the wrong choice, since inserting new textures can trigger a reallocation of the dynamic array and the copying of all textures. Not only is this slow, but also all references and pointers to the textures are invalidated. As mentioned before, we like to access the textures by an enum, so the associative container std::map looks like the perfect choice. The key type is our enumeration, the value type is the sf::Texture.

Note

The C++11 standard introduces strongly typed enumerations, also known as enum class. Unlike traditional enums, they do not offer implicit conversion to integers, and their enumerators reside in the scope of the enum type itself. Since C++11 is still being implemented by compiler vendors, not all features are widely supported yet. In this book, we focus on C++11 features that have already been implemented for a few years. Unfortunately, strongly typed enums do not fall into this category, that's why we do not use them in the book. If they are supported by your compiler, we still recommend using them.

We call our enum as ID, and let it contain three texture identifiers Landscape, Airplane, and Missile. We nest it into a namespace Textures. The namespace gives us a scope for the enumerators. Instead of writing just Airplane, we have Textures::Airplane which clearly describes the intention and avoids possible name collisions in the global scope:

namespace Textures
{
    enum ID { Landscape, Airplane, Missile };
}

We do not store the sf::Texture directly, but we wrap it into a std::unique_ptr.

Note

Unique pointers are class templates that act like pointers. They automatically call the delete operator in their destructor, thus they provide means of RAII for pointers. They support C++11 move semantics, which allow to transfer ownership between objects without copying. A std::unique_ptr<T> instance is the sole owner of the T object it points to, hence the name "unique".

Unique pointers give us a lot of flexibility; we can basically pass around heavyweight objects without creating copies. In particular, we can store classes that are non-copyable, such as, sf::Shader. Our class then looks as shown in the following code:

class TextureHolder
{
    private:
        std::map<Textures::ID, std::unique_ptr<sf::Texture>> mTextureMap;
};

The compiler-generated default constructor is fine, our map is initially empty. Same for the destructor, std::map and std::unique_ptr take care of the proper deallocation, so we do not need to define our own destructor.

Loading from files

What we have to write now is a member function to load a resource. It has to take a parameter for the filename and one for the identifier in the map:

void load(Textures::ID id, const std::string& filename);

In the function definition, we first create a sf::Texture object and store it in the unique pointer. Then, we load the texture from the given filename. After loading, we can insert the texture to the map mTextureMap. Here, we use std::move() to take ownership from the variable texture and transfer it as an argument to std::make_pair(), which constructs a key-value pair for the map:

void TextureHolder::load(Textures::ID id, const std::string& filename)
{
    std::unique_ptr<sf::Texture> texture(new sf::Texture());
    texture->loadFromFile(filename);

    mTextureMap.insert(std::make_pair(id, std::move(texture)));
}

Accessing the textures

So far, we have seen how to load resources. Now we finally want to use them. We write a method get() that returns a reference to a texture. The method has one parameter, namely the identifier for the resource. The method signature looks as follows:

sf::Texture& get(Textures::ID id);

Concerning the implementation, there is not much to do. We perform a lookup in the map to find the corresponding texture entry for the passed key. The method std::map::find() returns an iterator to the found element, or end() if nothing is found. Since the iterator points to a std::pair<const Textures::ID, std::unique_ptr<sf::Texture>>, we have to access its second member to get the unique pointer, and dereference it to get the texture:

sf::Texture& TextureHolder::get(Textures::ID id)
{
    auto found = mTextureMap.find(id);
    return *found->second;
}
Note

Type inference is a language feature that has been introduced with C++11, which allows the compiler to find out the type of expressions. The decltype keyword returns the type of an expression, while the auto keyword deduces the correct type at initialization. Type inference is very useful for complex types such as iterators, where the syntactic details of the declaration are irrelevant. In the following code, all three lines are semantically equivalent:

int         a = 7;
decltype(7) a = 7;   // decltype(7) is int
auto        a = 7;   // auto is deduced as int

In order to be able to invoke get() also, if we only have a pointer or reference to a const TextureHolder at hand, we need to provide a const-qualified overload. This new member function returns a reference to a const sf::Texture, therefore the caller cannot change the texture. The signature is slightly different:

const sf::Texture& get(Textures::ID id) const;

The implementation stays the same, so it is not listed again. Our class now looks as follows:

class TextureHolder
{
    public:
        void                  load(Textures::ID id,const std::string& filename);
        sf::Texture&          get(Textures::ID id);
        const sf::Texture&    get(Textures::ID id) const;
    private:
        std::map<Textures::ID,std::unique_ptr<sf::Texture>> mTextureMap;
};

Now the get() method is easy to use and can directly be invoked when a texture is requested:

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

sf::Sprite playerPlane;
playerPlane.setTexture(textures.get(Textures::Airplane));
主站蜘蛛池模板: 凤山县| 隆尧县| 内丘县| 榆中县| 高雄市| 石首市| 平邑县| 绩溪县| 利辛县| 横峰县| 东乡| 密山市| 泾源县| 绥宁县| 克拉玛依市| 常州市| 德令哈市| 濮阳县| 台北县| 皋兰县| 涟源市| 揭阳市| 贡嘎县| 梅州市| 东阳市| 光泽县| 隆子县| 曲松县| 巴青县| 松滋市| 新泰市| 建水县| 米易县| 蓬安县| 义乌市| 武安市| 鹤壁市| 滁州市| 安乡县| 龙山县| 通道|