meryngii.neta

今日も新たな"ネタ"を求めて。

参照カウントとバッファ再利用

std::shared_ptrの参照カウントを、バッファメモリの再利用に応用できるのではないかというお話。例として、単純なリソースクラスとそれのローダを考えてみます。

#include <vector>
#include <memory>
#include <numeric>

class Resource
{
public:
    std::size_t size() const { return data_.size(); }

    void resize(std::size_t size) { data_.resize(size); }

    std::vector<int>::iterator begin() { return data_.begin(); }
    std::vector<int>::iterator end() { return data_.end(); }

private:
    std::vector<int> data_;
};

class Loader {
public:
    std::shared_ptr<Resource> load();
    
private:
    std::shared_ptr<Resource> resource_;
};

std::shared_ptr<Resource> Loader::load()
{
    if (!resource_.unique()) // <===
        resource_ = std::make_shared<Resource>();
    
    resource_->resize(100);
    std::iota(resource_->begin(), resource_->end(), 0); // prepares data
    
    return resource_;
}

void f()
{
    Loader loader;
    
    // load and release
    for (int i = 0; i < 100; i++) {
        auto resource = loader.load();
        
        // "loader" reuses "resource_"
        // because "resource" is released here.
    }
}

void g()
{
    Loader loader;
    
    // load and store
    std::vector< std::shared_ptr<Resource> > gotResources;
    for (int i = 0; i < 100; i++) {
        auto resource = loader.load();
        gotResources.push_back(resource);
        
        // "loader" does not reuse "resource_"
        // because it's still shared with gotResources,
    }
    
    // ...
}

毎回同じデータが返ってくるくだらないコードですが…。
話の肝は<===と書いてあるところの2行だけです。要はshared_ptrの指している先がunique(=参照カウントが1)だったら、自分しか使ってない訳だからリソースを再利用してもいいじゃん、というわけです。
参照カウントが0(=何も持っていない)ときにもuniqueにはならないので、nullの場合も自動的に確保されます。
大きなデータを受け取る場合は、利用側であらかじめ一時バッファ変数を用意しておいて、内部処理でそこに入れてもらうというやり方が一番よく使われますし簡単です。しかし、メモリを再利用したい場合は利用側が適切に管理する必要がありますし、バッファの型も一つに固定されます。上の例のようにshared_ptrで利用者にバッファを返却し、その利用状況を逐一監視しておけば、もう少し柔軟に運用できるんじゃないかというわけです。
shared_ptrの参照カウントを、単なるメモリの確実な解放だけでなく別の面白い用途に使えないかなあと考えたお話でした。
もっとも、上の例は一番単純な例で、もう少しうまくやるにはバッファプールなどを作る必要があると思います。ちなみに、weak_ptrだと解放されてしまうのでうまくいきません。