初期化に時間がかかったり、そもそもメモリを確保する時間が問題になる場合、
一度確保したインスタンスは使い回したい。
毎回確保する
あるクラスA
のインスタンスが計算の一時領域として必要だとする。
void func(int N){
A a(N);
...
}
これだとこの関数が呼ばれるたびに初期化が必要となる。
この関数が何回も呼ばれるとすると、new/deleteのコストが無視できない事もある。
そんな場合にどうするかが今回の主題である。
静的に確保する
void func(int N){
static A a(N);
...
}
これで初期化は最初に呼ばれた時だけになる。
しかし問題が2つある:
- 初期化のための引数が最初の一回しか渡せない
- このような関数が複数あったとき、関数毎に静的変数が生成されメモリを沢山使う
関数オブジェクトにする
class func{
A a;
public:
func(int N): a(N) {}
void operator(){
...
}
};
これも静的に確保してるのと同様である。
一度確保したインスタンスを使い回す
以上の不満を解決するには、
一旦確保したインスタンスをその引数と共に覚えておく人がいて、
各関数はその人から一時的に借りてこれたらいい。
という事でこんなクラスを作る:
Rotary.hpp
# include <vector>
# include <memory>
# include <boost/any.hpp>
template <class T> class Rotary {
/** 確保したインスタンスを引数と共に保持する */
struct Item {
bool isFree; // 貸し出しているか
boost::any args; // 引数
T *ptr; // インスタンスのポインタ
};
std::vector<Item> items;
Rotary() {};
~Rotary() {
for (auto &i : items) {
delete i.ptr;
}
}
Rotary(const Rotary &) = delete;
Rotary(Rotary &&) = delete;
private:
/** 借りたメモリを返却する */
static void release(T *p) {
auto &man = getRef();
for (auto &i : man.items) {
if (p == i.ptr)
i.isFree = true;
}
}
public:
/** 自動的に返却される */
typedef std::unique_ptr<T, decltype(&release)> Pointer;
private:
/** 貸し出す */
template <typename... Args> Pointer rent_dyn(Args... args) {
typedef std::tuple<Args...> Tuple;
Tuple t = std::make_tuple(args...);
for (auto &i : items) {
if (i.isFree && typeid(Tuple) == i.args.type() &&
t == boost::any_cast<Tuple>(i.args)) {
i.isFree = false;
return Pointer(i.ptr, release);
}
} // 既に確保したインスタンスの内、貸し出していないのがあればそれを貸す
/* 無ければ新しく用意する */
T *mem = new T(args...);
items.push_back({ false, t, mem });
return Pointer(mem, release);
}
static Rotary<T> &getRef() {
static Rotary<T> rot; // singleton (スレッドセーフではない)
return rot;
}
public:
template <typename... Args> static Pointer rent(Args... args) {
return getRef().rent_dyn(args...);
}
};
# include "Rotary.hpp"
# include <iostream>
typedef Rotary<std::vector<int> > Rot;
void func1(int N) {
auto tmp1 = Rot::rent(N);
std::cout << tmp1->size() << std::endl;
std::cout << tmp1.get() << std::endl;
}
void func2(int N) {
auto tmp1 = Rot::rent(N);
auto tmp2 = Rot::rent(N);
std::cout << tmp1->size() << std::endl;
std::cout << tmp1.get() << std::endl;
std::cout << tmp2->size() << std::endl;
std::cout << tmp2.get() << std::endl;
}
int main(int argc, char const *argv[]) {
std::cout << "func1(10)" << std::endl;
func1(10);
std::cout << "func1(10)" << std::endl;
func1(10);
std::cout << "func2(10)" << std::endl;
func2(10);
std::cout << "func1(8)" << std::endl;
func1(8);
std::cout << "func1(10)" << std::endl;
func1(10);
return 0;
}
こんな風に各関数はロータリーからメモリを借りてこれる。
func1(10)
10
0x236c010 # 初確保
func1(10)
10
0x236c010 # 同じのを使う
func2(10)
10
0x236c010 # 一つ目は同じのを使う
10
0x236c0a0 # 既存のメモリは全部貸し出し中だから新しく確保
func1(8)
8
0x236c080 # 引数が違ったら別の領域に確保
func1(10)
10
0x236c010 # ちゃんと最初に確保したのを使ってる
のように同じメモリを使い回せている事が分かる。
課題
-
vector<Item>
をunordered_map
にする方が検索が早いはず - 複数の翻訳単位があるときの静的変数
static Rotary<T> rot
の扱い