開発ガイドの目次に戻る

多数インスタンスのメモリ管理

はじめに

敵や弾など、同じクラスのインスタンスが多数作成される場合には、あらかじめまとまった数のインスタンスのためのメモリを確保しておき、効率的なゲーム実行ができるようにしておきます。Karakuri Framework では、KRMemoryAllocator クラスがそのための機能を提供します。

主な使い方

ここでは、単一のクラスのインスタンスを複数個管理する場合について説明します。

例として、次のような Bullet クラスを利用することを考えます。Bullet クラスの複数のインスタンスをまとめて管理するための BulletManager クラスも用意しています。

重要:アロケータの管理対象となるすべてのクラスは、明示的なデストラクタの実装が必要です。要・不要を問わず、管理対象の Bullet クラスにデストラクタを明示的に用意していることに注意してください。

// Bullet.h

#pragma once

#include <Karakuri/Karakuri.h>

class Bullet {
    KRVector2D  mPos;

public:
    Bullet(const KRVector2D& pos);
    ~Bullet();  // 必ずデストラクタを用意すること。

public:
    void    move();
    void    draw();
};

class BulletManager {
    std::list<Bullet*>  mBullets;

public:
    BulletManager();
    ~BulletManager();

public:
    Bullet* addBullet(const KRVector2D& pos);
};
// Bullet.cpp

#include "Bullet.h"

Bullet::Bullet(const KRVector2D& pos)
    : mPos(pos)
{
    // Do nothing
}

Bullet::~Bullet()
{
    // Do nothing
}

void Bullet::move()
{
    mPos.x += 3;
}

void Bullet::draw()
{
    // テクスチャを使って描画
}


BulletManager::BulletManager()
{
    // Do nothing
}

BulletManager::~BulletManager()
{
    for (std::list<Bullet*>::iterator it = mBullets.begin(); it != mBullets.end(); it++) {
        delete *it;
    }
}

Bullet* BulletManager::addBullet(const KRVector2D& pos)
{
    Bullet* newBullet = new Bullet(pos);
    mBullets.push_back(newBullet);
    return newBullet;
}

1. アロケータの用意

まずは、Bullet.h にアロケータのための変数を宣言します。

extern KRMemoryAllocator* bulletAllocator;

Bullet.cpp に変数の実体を用意します。

KRMemoryAllocator* bulletAllocator = NULL;

2. アロケータ使用の宣言

Bullet クラスの宣言部の先頭で KR_DECLARE_USE_ALLOCATOR() マクロを使って、上記のようにして用意した bulletAllocator の使用を宣言します。

class Bullet {
    KR_DECLARE_USE_ALLOCATOR(bulletAllocator)

    KRVector2D  mPos;
    ...

3. アロケータの生成

BulletManager クラスのコンストラクタで、Bullet クラスのサイズと、インスタンスの最大生成個数を指定して、アロケータを生成します。以下の例では、最大512個のインスタンスを作れるようにアロケータを生成しています。

BulletManager::BulletManager()
{
    bulletAllocator = new KRMemoryAllocator(sizeof(Bullet), 512, "bullet-alloc");
}

4. 完成

BulletManager クラスのデストラクタで、アロケータの解放を行うのを忘れないでください。

BulletManager::~BulletManager()
{
    delete bulletAllocator;
}

以上で、ゲーム開始時にまとまったメモリを確保しておき、ゲーム実行時にそのメモリを使いながら効率的にインスタンスの作成ができるようになります。その他の部分は、いっさい変更する必要はありません。以下に、完成版のリストを掲載します。追加部分を強調表示しています。

// Bullet.h

#pragma once

#include <Karakuri/Karakuri.h>

extern KRMemoryAllocator* bulletAllocator;

class Bullet {
    KR_DECLARE_USE_ALLOCATOR(bulletAllocator)

    KRVector2D  mPos;

public:
    Bullet(const KRVector2D& pos);
    ~Bullet();  // 必ずデストラクタを用意すること。

public:
    void    move();
    void    draw();
};

class BulletManager {
    std::list<Bullet*>  mBullets;

public:
    BulletManager();
    ~BulletManager();

public:
    Bullet* addBullet(const KRVector2D& pos);
};
// Bullet.cpp

#include "Bullet.h"


KRMemoryAllocator* bulletAllocator = NULL;


Bullet::Bullet(const KRVector2D& pos)
    : mPos(pos)
{
    // Do nothing
}

Bullet::~Bullet()
{
    // Do nothing
}

void Bullet::move()
{
    mPos.x += 3;
}

void Bullet::draw()
{
    // テクスチャを使って描画
}


BulletManager::BulletManager()
{
    // Do nothing
    bulletAllocator = new KRMemoryAllocator(sizeof(Bullet), 512, "bullet-alloc");
}

BulletManager::~BulletManager()
{
    // メモリの解放はアロケータで行われるので、厳密にはここの部分が不要になる。
    for (std::list<Bullet*>::iterator it = mBullets.begin(); it != mBullets.end(); it++) {
        delete *it;
    }

    delete bulletAllocator;
}

Bullet* BulletManager::addBullet(const KRVector2D& pos)
{
    Bullet* newBullet = new Bullet(pos);
    mBullets.push_back(newBullet);
    return newBullet;
}

複数の派生クラスを利用する場合

あるクラスから派生した複数のクラスのインスタンスを複数個管理する場合、基底クラスで KR_DECLARE_USE_ALLOCATOR() マクロを使ってアロケータの使用を宣言しておけば、派生クラスでアロケータの使用を宣言する必要はありません。

ただし、各派生クラスが異なるサイズをもつ場合がありますので、以下のように、KR_UPDATE_MAX_CLASS_SIZE() マクロを使用してクラスの最大サイズを求めた上でアロケータを生成する点だけが、単一クラスを利用する場合と異なります。以下の例では、Bullet クラスからの派生クラスとして Bullet1, Bullet2, Bullet3 という3つのクラスがあることとしています。

BulletManager::BulletManager()
{
    size_t maxBulletClassSize = 0;
    
    KR_UPDATE_MAX_CLASS_SIZE(maxBulletClassSize, Bullet1);
    KR_UPDATE_MAX_CLASS_SIZE(maxBulletClassSize, Bullet2);
    KR_UPDATE_MAX_CLASS_SIZE(maxBulletClassSize, Bullet3);
    
    bulletAllocator = new KRMemoryAllocator(maxBulletClassSize, 512, "bullet-alloc");
}

重要:なお、単一のクラスを利用する場合と同様に、要・不要を問わず、基底クラスでも各派生クラスでも、明示的なデストラクタの実装が必要です。