0

私は新しい独学のコーダーで、プログラムの設計と構造に問題があります。基本的な問題は、システムのカプセル化を壊すことなく、API を通じて提供されたリソース (たとえば、glDeleteBuffers 関数を使用して解放する必要がある OpenGL バッファーなど) を解放することです。私のセットアップの基本的な概要を説明しましょう。

まず第一に、私のプログラムは「Engine」と呼ばれる大きな「ファサード」クラスを使用します。このクラスには、グラフィックス レンダリング、オーディオ ストリーミング、イベント処理などのためのいくつかのサブシステムが含まれています。私の設計の背後にある主なアイデアの 1 つは、各サブシステムが可能な限りカプセル化。OpenGL 呼び出しは、グラフィックス レンダリング システム内、オーディオ内の OpenAL 呼び出しなどにのみ存在します。

また、リソースの割り当てと解放にも注意したかったので、プログラムの他の部分で「new」を使用してさまざまなアセット (画像、サウンドなど) を作成する代わりに、Engine クラス内にいくつかのファクトリ メソッドを設定して、オブジェクトを適切なサブシステムに登録し (たとえば、OpenGL に適切にバッファリング/登録できるグラフィックス サブシステムに画像オブジェクト参照を送信する)、それらのオブジェクトへのスマート ポインターを返します。

class Engine     //..simplified for sake of example..//
{
private:
    GraphicsSystem graphicsRenderer;
    AudioSystem audioRenderer;
    //..window, events, etc...//

public:
    //ctors.. dtor.. tons of other methods..//

    std::shared_ptr<Image> createImage( /*filepath, size info, other params*/ );

    /*..both of these call equivalent graphics system functions:*/

    bool registerImage( Image & tempImage );    //allocates and registers texture resources..
    bool deregisterImage( Image & tempImage );  //frees those same resources upon object destruction..
};

これは、ファクトリ メソッドの 1 つがどのように見えるかの例です。

std::shared_ptr<Image> Engine::createImage( /*params*/ )
{
    Image * tempImagePtr = new Image( /*params*/ ); //create..

    registerImage( *tempImagePtr ); //register..

    return std::shared_ptr<Image>( tempImagePtr ); //return..
}

しかし、ここに私が抱えている主な問題があります: registerImage 関数を使用して割り当てられたリソースを解放するのが難しいです! クライアントがエンジンから資産オブジェクトを取得することのみを担当し、リソースの取得と管理の詳細が抽象化され、資産オブジェクトが破棄されたときに自動的に処理されるように、コードを機能させたい..

ここに私が試した/検討したいくつかのオプションがあります:

  • オブジェクトが削除される前に、クライアント (Engine クラスのユーザー) が適切な登録解除関数を呼び出す責任を負うようにします。このオブジェクトは、前もってコーディングするのは非常に簡単ですが、ファクトリ メソッドを介して RAII スタイルのオブジェクトを作成することのポイントを実際に無駄にしてしまいます。これは、クライアントが単純に「アセットを取得して忘れる」ことを許可するという私の目標を実現するのに役立ちません。

  • Engine の単一インスタンス全体への参照を各アセット クラスに渡し、アセット クラスがその参照を使用してデストラクタ内の登録解除関数を呼び出せるようにします。これは、適切な自動リソースクリーンアップが正しく機能していることを確認するためのちょっとした「ハック」として、以前に試したソリューションです。それは~うまくいきました~が、アセットにすべてのエンジン呼び出しへのアクセスを許可するという考えは好きではありませんでした。エンジンに自分自身を登録/登録解除する機能だけが必要な場合に、各アセットにそのようなアクセスを与えるのは、やり過ぎのように思えました!

  • 「コードをクリーンアップ」し、代わりに適切な登録および登録解除関数へのポインターを資産オブジェクトに渡すことで、その状況を改善しようとしました。このようにして、オブジェクトは構築時に自分自身を登録し、破棄時に自分自身を登録解除できます。試すまでは、これは良い計画のように思えました..これまでのところ、アセット ファクトリ メソッドを介してエンジンの非スタティック メソッドをアセット オブジェクトに渡すのに非常に苦労しました。C++ の構文は混乱を招きます。私は関数ポインターの経験がなく (非静的メソッドへのポインターを渡すことは言うまでもありません)、非常にイライラするので、ずさんで抽象化を破る引き渡しの戦術に戻ることを検討しています。 Engine インスタンス全体への参照です! メソッド名を使用してみました。メソッド名の前に「&」を追加して、「this.」を追加してみました。メソッド名の前のキーワードなど。

私の Image アセット コンストラクターの 1 つを次に示します。

Image( std::string filepath , 
       void (*ptrRegFunc)(unsigned int * tempTextureID, const sf::Image * const tempSourceImage) , 
       void (*ptrDeregFunc)(unsigned int * tempTextureID) );

Image クラスには同じ型の 2 つのポインターが含まれており、ポインターを渡そうとするまでは最初は問題ないように見えました...

std::shared_ptr<Image> Engine::createImage( std::string filePath )
{
    Image * tempImagePtr = new Image( filePath, &registerImage, &deregisterImage ); //create and send func ptrs..

    return std::shared_ptr<Image>( tempImagePtr ); //return..
}

もちろん、それはコンパイルされませんでした。その理由は理解できます。どのエンジン オブジェクト/インスタンスが参照されているのかが明確ではありません。しかし、私はそれを調べて、これらのさまざまな C++ 構文スタイルをすべて試しました。

&(this->registerImage)

&((*this).registerImage)

何も機能しておらず、構文が非常にひどいため、問題を起こす価値がほとんどないように思えます!

でも今はまた振り出しに戻ったような気がします。だから私は他の可能性を見てきました。私の頭では、アイデアは非常に単純に思えます。各アセット オブジェクトに、それ自体をエンジン オブジェクトに登録および登録解除する機能を与えたいだけです。

各アセットに Engine クラス インターフェイス全体へのアクセスを許可せずに、私の目標を達成するためのより良い/簡単な方法はありますか? Command Patternについて少し読んだことがありますが、これは私が探しているもののように思えますが、そのパターンを実装する方法や、それが本当に必要なものであるかどうかは正確にはわかりません。もちろん、関数オブジェクトの概念にも出くわしました..それらのいずれかが私の問題の解決策を提供しますか?

とにかく、これについて私にむき出しにしてくれてありがとう、私はまだ学んでいて、これはおそらくそこにいるあなたの多くにとって単純なことだと知っています. 私が何を望んでいるか、または現在のシステムが何を使用しているかについて他に説明できることがあれば、お知らせください。できるだけ早く更新します

4

2 に答える 2