LoginSignup
13
12

More than 5 years have passed since last update.

C++11 の make_shared を拡張する

Posted at

概要

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 内に各種関数を実装します。

sub_memory.hpp
#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_

テスト

コンストラクタを公開しないクラスを定義し、実装した関数に渡してみましょう。

ソース

sub_memory-test.cpp
#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 にしましたが、あまりクールではありません。
物凄くどうでもいいですし、組み込む際に各々が変えればいいですが、もっといい名前があれば教えてください。

その他

実装やテストで気が付いたことがあればご意見頂けると幸いです。

13
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
12