4

shared_ptr でカスタムのデリータを使用する適切な方法について、私はまだ少し混乱しています。リソース割り当てを追跡する ResourceManager クラスがあり、Release メソッドを非公開にし、ResourceHolder を返す Allocate メソッドを作成することで、使用済みリソースの自動解放をサポートするようにそのインターフェイスを変更しました。

// ResourceManager.cpp:
public:
    ResourceHolder<Resource> Allocate(args);

private:
    void Release(Resource*);

そして、私が実装する ResourceHolder クラスは次のようになります。

// ResourceHolder.h
template <typename T>
class ResourceHolder
{
public:
    ResourceHolder(
        _In_ T* resource,
        _In_ const std::function<void(T*)>& cleanupFunction)
        : _cleanupFunction(cleanupFunction)
        , _resource(resource, [&](T* resource)
        { 
            cleanup(resource); 
        }) // Uses a custom deleter to release the resource.
    {
    }

private:
    std::function<void(T*)> _cleanupFunction;
    std::shared_ptr<T> _resource;
};

// ResourceManager::Allocate()
...
return ResourceHolder<Resource>(new Resource(),[this](Resource* r) { Release(r); });
  1. 私のクリーンアップ メソッドでは、T を削除する必要がありますか? それを行うことは常に安全ですか?

    if (nullptr != T) delete T;
    
  2. cleanup() が例外をスローできる場合はどうなりますか? 状況によってはスコープから逃れることができますか、それとも常に防止する必要がありますか?

  3. 私の ResourceManager は、私が使用しているトレース ライブラリに依存していません。そのため、呼び出し元がコンストラクターを介して提供でき、リリース メソッドで呼び出されるコールバックを選択しました。したがって、私のリリースは次のようになります。

    void Release(Resource* r)
    {
        shared_ptr<std::Exception> exc = nullptr;
        try
        {
            // Do cleanup.
        }
        catch(Exception* ex)
        {
            exc.reset(ex);
        }
    
        if (nullptr != r) delete r;
    
        // Is it now safe to throw?
        if (nullptr != m_callback)
            m_callback(args, exc);
    }
    
    void Callback(args, shared_ptr<std::Exception> ex)
    {
        // Emit telemetry, including exception information.
    
        // If throwing here is ok, what is the correct way to throw exception here?
        if (nullptr != ex)
        {
            throw ex;
        }
    }
    

これは健全な設計アプローチですか?

4

1 に答える 1

2

私のクリーンアップ メソッドでは、T を削除する必要がありますか? それを行うことは常に安全ですか?

ポインターがインスタンス化されたオブジェクトを参照する場合newは、呼び出す必要がありますdelete。そうしないと、メモリ リークと未定義の動作が発生します。

cleanup() が例外をスローできる場合はどうなりますか? 状況によってはスコープから逃れることができますか、それとも常に防止する必要がありますか?

そうであってはならず、そうならないようにあらゆる努力をする必要があります。ただし、クリーンアップ コード例外をスローする場合は、それをキャッチして適切に処理し、食べる必要があります。その理由は、デストラクタのコンテキスト内でカスタム デリータを呼び出すことができ、例外が既に伝播されている間にデストラクタが呼び出される可能性が常にあるためです。例外が既に進行中で、キャッチされていない別の例外がスローされた場合、アプリケーションは終了します。つまり、カスタムのデリータとクリーンアップ コードをデストラクタであるかのように扱い、例外処理に関する同じ規則とガイドラインに従います。

効果的な C++項目 #8 - 例外がデストラクタから離れないようにする

デストラクタは決して例外を発行すべきではありません。デストラクタで呼び出された関数がスローされる可能性がある場合、デストラクタは例外をキャッチしてから、それらを飲み込むか、プログラムを終了する必要があります。

§ 15.1/7 C++ 標準 [except.throw]

スローされる式の評価が完了した後、例外がキャッチされる前に、例外処理メカニズムが呼び出された場合、例外を介して終了する関数std::terminateが呼び出されます。

-

これは健全な設計アプローチですか?

あなたが現在例外を処理しようとしている方法を除いて、私はそれに問題はないと思います。実際に変更する必要があるのは、コールバックを呼び出す方法と、コールバックが渡された例外を処理する方法だけです。変更後の結果のコードは、次のようになります。

void Release(Resource* r)
{
    try
    {
        // Do cleanup.
    }
    catch (Exception& ex)
    {
        // Report to callback
        if (nullptr != m_callback)
            m_callback(args, &ex);

        // Handle exception completely or terminate

        // Done
        return;
    }

    // No exceptions, call with nullptr
    if (nullptr != m_callback)
        m_callback(args, nullptr);
}

void Callback(args, const Exception* ex)
{
    // Emit telemetry, including exception information.

    //  DO NOT RETHROW ex
}
于 2015-01-02T21:20:18.723 に答える