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

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));
主站蜘蛛池模板: 福鼎市| 桓台县| 简阳市| 宣化县| 张家港市| 景东| 孝义市| 赫章县| 垣曲县| 汝州市| 辽中县| 青阳县| 元朗区| 左贡县| 桂平市| 华坪县| 肇庆市| 富宁县| 高雄市| 大洼县| 正阳县| 东乡族自治县| 宁陕县| 漾濞| 平和县| 韶关市| 长宁县| 钟山县| 南投县| 岗巴县| 拉萨市| 高清| 永顺县| 霍林郭勒市| 象州县| 湘潭市| 庄河市| 黎平县| 濮阳市| 加查县| 板桥市|