にわかプラス

にわかが玄人になることを夢見るサイトです。社会や国際のトレンド、プログラミングや電子工作のことについて勉強していきたいです。

C++で使えるDIコンテナライブラリ「Hypodermic」が便利

sponsor

C++で使えるDIコンテナがないかと探してみたところ、Hypodermicというライブラリがあった。

github.com

タグを見ると2016年から開発が始まっており、最新リリースは2019年。
ちゃんとメンテされ続けているみたい。

特徴の1つとして、ヘッダーオンリーライブラリなので取り回しが非常に良い。

ちなみに、GitHubの1行目で、HypodermicをIoC Containerとして紹介している。広義の意味でIoC Container = DI Container と思っておけば良さそう。
参考
IoCとService LocatorとDIの関係 - yotiky Tech Blog

使用環境

  • Window10
  • Visual studio 2019
  • Boost 1.7.3 (Boostないとエラーが出る)

使い方

どんなものかサクッと試したかったので、もともとのHypodermicプロジェクトに、Mainとなるプロジェクトを追加した。

  1. GitHubからHypodermic-masterリポジトリをクローン
  2. クローンしたディレクトリ直下のHypodermic.slnを起動
  3. Hypodermicプロジェクトに、Mainとなるプロジェクト(SampleClientとする)を追加
  4. SampleClientの追加のインクルードディレクトリにHypodermic-masterのパスを追加する
    • 例)C:\Hypodermic-master
  5. SampleClientの追加のインクルードディレクトリにBoostのパスを追加する
    • 例)C:\Boost\include\boost-1_73
  6. SampleClientにサンプルコードを追加する

あるメッセージ → IMessageWriter → ConsoleMessagesWriter → IMessageSerializer → LengthPrefixedMessageSerializer → コンソールに出力 という流れの処理を行わせたい。

// メッセージを出力するためのクラスのインタフェース
//標準出力とメッセージを受け取る
class IMessageSerializer
{
public:
    virtual ~IMessageSerializer() = default;

    virtual void serialize(const std::string& message, std::ostream& stream) = 0;
};

// 引数から受けった標準出力の方法で、受け取ったメッセージを出力する
// ここではメッセージのサイズとメッセージ内容を標準出力に出す
class LengthPrefixedMessageSerializer : public IMessageSerializer
{
public:
    void serialize(const std::string& message, std::ostream& stream) override
    {
        stream << message.size();
        stream << message;
    }
};

// メッセージを何らかの形で出力することを意味するインタフェース
class IMessageWriter
{
public:
    virtual ~IMessageWriter() = default;

    virtual void write(const std::string& message) = 0;
};

// 標準出力として出力する具象クラス
// IMessageSerializerにメッセージと標準出力の方法を渡して処理を委任している
class ConsoleMessageWriter : public IMessageWriter
{
public:
    explicit ConsoleMessageWriter(std::shared_ptr< IMessageSerializer > serializer)
        : m_serializer(serializer)
    {
    }

    void write(const std::string& message) override
    {
        m_serializer->serialize(message, std::cout);
    }

private:
    std::shared_ptr< IMessageSerializer > m_serializer;
};

Applicationクラスは先程の以下処理を行わせたいので、コンストラクタで依存関係を定義しコンテナに格納する。

IMessageWriterに対してConsoleMessagesWriterを具象として指定。 IMessageSerializerに対してLengthPrefixedMessageSerializerを具象として指定。

#include "Hypodermic/Hypodermic.h"

class Application
{
public:
    Application()
    {
        // 各クラスが格納するコンテナを作成
        Hypodermic::ContainerBuilder builder;

        // IMessageSerializer に対してどの具象クラスを作成するかを指定
        // ここではLengthPrefixedMessageSerializerを指定する
        builder.registerType< LengthPrefixedMessageSerializer >()
               .as< IMessageSerializer >();

        // IMessageWriter に対して ConsoleMessageWriter を指定する
        builder.registerType< ConsoleMessageWriter >().as< IMessageWriter >();

        // 上記で指定した組み合わせを生成する
        m_container = builder.build();
    }

    // 実行関数
    void run(){
    // コンテナがIMessageWriterを返す
    auto messageWriter = m_container->resolve< IMessageWriter >();

    // 出力するメッセージを指定
    messageWriter->write("The app is running");
    };

private:
    std::shared_ptr< Hypodermic::Container > m_container;
}

Main文。

#include "Hypodermic/Hypodermic.h"
#include "Application.h"

int main()
{
    Application obj_app;
    obj_app.run(); //  「18The app is running」 と出力される

    return 0;

}

おわりに

C++でDIコンテナをしたいときは、Hypodermicを使えば良さそう。