shared_ptr<T> は、メモリを自動的に解放してくれるスマートポインタの一種で、
カスタムデリータを設定できるようになっています。
本記事では、カスタムデリータ(Custom Deleter)を指定する方法をご紹介します。
-
-
参考スマートポインタとは?C++での使用方法とメリット
C++ではポインタを使ってメモリの内容を書き換えたり、同じメモリを参照したりします。しかし、メモリリークや解放済みのメモリにアクセス、メモリの2重解放、などの問題に悩まされます。本記事では、これらの悩みを解決するスマートポインタについて紹介します。
続きを見る
参考
shared_ptr<T> については「スマートポインタとは?」で詳しく解説しています。
C++11 で実装された機能で、メモリリークや2重解放を回避する手法として利用されています。
カスタムデリータとは?
shared_ptr<T> が所有するポインタへの参照がゼロになったタイミングで自動的に解放されます。
解放の際に呼び出されるデフォルトのデリータは、delete となっています。
例えば、以下のような場合、delete を呼び出すだけでは正しい終了処理となりません。
1 2 3 4 5 |
#include <memory> #include <stdio.h> std::shared_ptr<FILE> fp(fopen("test.txt", "r")); |

このような場合に、カスタムデリータを指定することで解決することができます。
その名の通り、解放の際に実行されるデリータに、独自の処理を指定することができます。
カスタムデリータを指定する方法
deleter 関数を使用
既存の関数や独自に実装した関数を deleter として指定することができます。
先ほどの fopen の場合は、以下のように記述することで fclose が呼び出されます。
1 2 3 4 5 |
#include <memory> #include <stdio.h> std::shared_ptr<FILE> fp(fopen("test.txt", "r"), fclose); |

typename の生ポインタが渡されます。
独自に記述した deleter を指定する場合は、以下のように記述します。
1 2 3 4 5 6 7 8 9 |
#include <memory> #include <stdio.h> struct MyDeleter { void operator()(FILE* fp) const { fclose(fp); } }; std::shared_ptr<FILE> fp(fopen("test.txt", "r"), MyDeleter()); |
ラムダ式を使用
shared_ptr<T> と同じくして C++11 で導入された Lambda 式(ラムダ式)で指定することもできます。
1 2 3 4 5 6 7 |
#include <memory> #include <stdio.h> std::shared_ptr<FILE> fp(fopen("test.txt", "r"), [](FILE* fp) { fclose(fp); }); |
カスタムデリータの利用例
先ほどの fopen / fclose も一例ですが、他にも便利に利用できるケースがあります。
ここでは、別プロセスを起動する popen / pclose の例を紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <memory> #include <stdio.h> std::string ExecuteCommand (const char* cmd) { char buffer[128]; std::string result; std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose); if (!pipe) throw std::runtime_error("popen() failed!"); while (!feof(pipe.get())) { if (fgets(buffer, sizeof(buffer), pipe.get()) != NULL) result += buffer; } return result; } int main(void) { std::string ret = ExecuteCommand("ls -l"); printf("result:\n%s\n", ret.c_str()); return 0; } |

起動したプロセスの終了ステータスを取得したい場合は、ラムダ式を利用して以下のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <memory> #include <stdio.h> bool ExecuteCommand(const char* cmd, std::string& stdOut, int& exitCode) { char buf[128]; std::shared_ptr<FILE> pipe(popen(cmd, "r"), [&](FILE* p) { exitCode = pclose(p); }); if (!pipe) { return false; } while (!feof(pipe.get())) { if (fgets(buf, sizeof(buf), pipe.get()) != NULL) stdOut += buf; } return true; } int main(void) { int exitCode; std::string stdOut; if (ExecuteCommand("ls -l", stdOut, exitCode) != true) return -1; printf("result:%d\n%s\n", exitCode, stdOut.c_str()); return 0; } |

カスタムデリータの注意点
shared_ptr<T> でカスタムデリータを指定する場合、注意すべきことがあります。
- make_shared を使用できない
カスタムデリータは、shared_ptr<T>のコンストラクタにのみ指定できます。
そのため、make_sharedを利用することはできなくなります。 - 同じオブジェクトに指定できるカスタムデリータは1つ
カスタムデリータを指定したshared_ptr<T>をさらにshared_ptr<T>で参照する場合、
2つ目のスマートポインタへは、カスタムデリータを指定することができません。
1つのオブジェクトに対して、1つのみデリータを指定することができます。
おわりに
いかがでしたでしょうか。
今回は、shared_ptr<T> にカスタムデリータを指定する方法を紹介しました。
C言語で書いた場合と比較して、エラー処理が書きやすくなると感じていただけましたでしょうか?
C++を使用する場合は、言語仕様を活用することで、効率よい設計・実装を行えます。
参考になりましたらうれしいです。