概要
std::make_shared<T>
は std::shared_ptr<T>
を生成するヘルパ関数です。
ユーザーオブジェクトと参照カウンタをまとめて生成するので、より効率的にメモリ確保してくれます。
しかし、全てこれで生成すればいいというわけではありません。
いくつか落とし穴や制限があります。
- 型 T のコンストラクタが公開されていないと、コンパイルエラーになる。
- カスタムデリータ、カスタムアロケータが指定できない。
勿論それぞれ回避策はありますが、いちいち書くのも面倒ですので、それらを回避する関数を作りましょう。
ついでに、C++11 に無い make_unique
も実装してしまいましょう。
仕様
template <class T, typename... Args> std::shared_ptr<T> make_shared(Args&&... args)
std::make_shared<T>
とほぼ同じ働きをします。
唯一違うのは、型 T のコンストラクタが公開されていなくても、コンパイルエラーにならないことです。
ファクトリの実装が捗りますね。
template <class T, class Deleter, typename... Args> std::shared_ptr<T> make_shared(custom_deleter, Deleter d, Args&&... args)
タグディスパッチによる、カスタムデリータ指定版です。
残念ながら、内部的には単に new してカスタムデリータを渡しているだけなので、本来の std::make_shared<T>
の恩恵は受けられません。
template <class T, class Deleter, class Alloc, typename... Args> std::shared_ptr<T> make_shared(custom_full, Deleter d, Alloc a, Args&&... args)
同じく、タグディスパッチによる、カスタムデリータとアロケータ指定版です。
注意点も同じです。
template <class T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args)
std::make_shared<T>
の std::unique_ptr<T>
版です。
C++14 での std::make_unique<T>
とインターフェイスは同じですが、内部的には単に new しているだけです。
故に効率的なメモリ確保の恩恵は受けられませんが、書式を統一しておけば後々いくらでも調整できるでしょう。
template <class T, class Deleter, typename... Args> std::unique_ptr<T> make_unique(custom_deleter, Deleter d, Args&&... args)
タグディスパッチによる、カスタムデリータ指定版です。
ベースになっている make_unique<T>
と実装はほぼ同じなので、特に注意点はありません。
実装
名前空間 sub 内に各種関数を実装します。
#ifndef SUB_MEMORY_HPP_
#define SUB_MEOMRY_HPP_
#include <memory>
namespace sub {
namespace {
// ctor を public にするためだけの継承クラス
template <class T>
struct _derived : public T {
template <typename... Args>
_derived(Args&&... args) : T(std::forward<Args>(args)...) {}
};
} // namespace
// カスタムデリータ使用タグ
struct custom_deleter {};
// カスタムデリータ、アロケータ使用タグ
struct custom_full {};
// make_shared 拡張版 カスタムデリータ指定
template <class T, class Deleter, typename... Args>
std::shared_ptr<T> make_shared(custom_deleter, Deleter d, Args&&... args) {
return std::move(std::shared_ptr<T>(new _derived<T>(std::forward<Args>(args)...), d));
}
// make_shared 拡張版 カスタムデリータ、アロケータ指定
template <class T, class Deleter, class Alloc, typename... Args>
std::shared_ptr<T> make_shared(custom_full, Deleter d, Alloc a, Args&&... args) {
return std::move(std::shared_ptr<T>(new _derived<T>(std::forward<Args>(args)...), d, a));
}
// make_shared 拡張版
template <class T, typename... Args>
std::shared_ptr<T> make_shared(Args&&... args) {
return std::move(std::make_shared<_derived<T>>(std::forward<Args>(args)...));
}
// make_unique(C++14) 拡張版 カスタム
template <class T, class Deleter, typename... Args>
std::unique_ptr<T> make_unique(custom_deleter, Deleter d, Args&&... args) {
return std::move(std::unique_ptr<T>(new _derived<T>(std::forward<Args>(args)...), d));
}
// make_unique(C++14) 拡張版
template <class T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::move(std::unique_ptr<T>(new _derived<T>(std::forward<Args>(args)...)));
}
} // namespace sub
#endif // SUB_MEMORY_HPP_
テスト
コンストラクタを公開しないクラスを定義し、実装した関数に渡してみましょう。
ソース
#include <stdio.h>
#include <string>
#include "sub_memory.hpp"
// ctor が公開されていない適当なクラス
class foo {
public:
~foo() { printf("~foo()\n"); }
void f() { printf("f() "); }
protected:
foo() { printf("foo() "); }
foo(int i) { printf("foo(%d) ", i); }
foo(std::string &str) {
printf("foo(%s) ", str.empty() ? "*empty*" : str.c_str());
}
};
int main() {
// ctor が公開されていないので直接インスタンス化はできない
#if 0
foo a;
foo *p = new foo();
#endif
// make_shared + 引数なし
printf("0: ");
if (auto it = sub::make_shared<foo>()) {
printf("shared ");
it->f();
} else {
printf("*** error ***\n");
}
// make_shared + 引数あり
printf("1: ");
if (auto it = sub::make_shared<foo>(1234)) {
printf("shared ");
it->f();
} else {
printf("*** error ***\n");
}
// make_shared + 引数あり + カスタムデリータ
printf("2: ");
if (auto it = sub::make_shared<foo>(sub::custom_deleter(), std::default_delete<foo>(), 456)) {
printf("shared custom_deleter ");
it->f();
} else {
printf("*** error ***\n");
}
// make_shared + 引数あり + カスタムデリータ + カスタムアロケータ
printf("3: ");
if (auto it = sub::make_shared<foo>(sub::custom_full(), std::default_delete<foo>(), std::allocator<void>(), 789)) {
printf("shared custom_full ");
it->f();
} else {
printf("*** error ***\n");
}
// make_unique + 引数なし
printf("4: ");
if (auto it = sub::make_unique<foo>()) {
printf("unique ");
it->f();
} else {
printf("*** error ***\n");
}
// make_unique + 引数あり(参照)
printf("5: ");
std::string str("bar");
if (auto it = sub::make_unique<foo>(str)) {
printf("unique ");
it->f();
} else {
printf("*** error ***\n");
}
// make_unique + 引数あり(コピー)
printf("6: ");
#if 0
// コピー渡し ctor が用意されていないのでコンパイルエラー
if (auto it = sub::make_unique<foo>(std::string("baz"))) {
printf("unique ");
it->f();
#else
if (false) {
#endif
} else {
printf("*** error ***\n");
}
// make_unique + 引数あり + カスタムデリータ
printf("7: ");
if (auto it = sub::make_unique<foo>(sub::custom_deleter(), std::default_delete<foo>(), 42)) {
printf("unique custom_deleter ");
it->f();
} else {
printf("*** error ***\n");
}
return 0;
}
出力
以下のように出力されます。
0: foo() shared f() ~foo()
1: foo(1234) shared f() ~foo()
2: foo(456) shared custom_deleter f() ~foo()
3: foo(789) shared custom_full f() ~foo()
4: foo() unique f() ~foo()
5: foo(bar) unique f() ~foo()
6: *** error ***
7: foo(42) unique custom_deleter f() ~foo()
課題
カスタムデリータやアロケータの指定をもっとスマートに出来ないか
現在タグディスパッチを使っていますが、関数の名前を変えるだけでもよかったかもしれません。
よりよい実装方法があれば教えて頂けると助かります。
名前空間をもっとかっこよく
とりあえず sub にしましたが、あまりクールではありません。
物凄くどうでもいいですし、組み込む際に各々が変えればいいですが、もっといい名前があれば教えてください。
その他
実装やテストで気が付いたことがあればご意見頂けると幸いです。