1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[日記] Strategy patternをC++のtemplateで書きたい

Last updated at Posted at 2025-02-07

背景

  • strategy patternというと通常interfaceを作ってその実装で具体的な振る舞いを定義することになる
  • C++でやろうとすると抽象クラスを作ることになると思うが、リソースが限られている環境だとvtableのオーバーヘッドが気になる
  • そのためtemplateを使ってstrategyっぽいことを実現したい

実装例

  • 例としてHttpのリクエストを受け付けるクラスを考える
  • StrategyクラスとしてHttpMethodクラスを用意する
    • HttpMethodクラスはHttpGetRequestまたはHttpPostRequestで初期化される
  • ConcreateStrategyクラスはなく、初期化時に使われた型によってふるまいを変える
#include <string>
#include <iostream>

struct HttpGetRequest
{
    std::string header;
};

struct HttpGetResponse
{
    std::string header;
    std::string body;
};

struct HttpPostRequest
{
    std::string header;
    std::string body;
};

struct HttpPostResponse
{
    std::string header;
    std::string body;
};


// Strategyにあたるクラス
// 個々のメソッドに対応するクラスは作らず、メンバ関数をtemplate化することで振る舞いを変える
template<class T, class U>
class HttpMethod
{
    public:
        HttpMethod(const T &request);
        bool request(U &response);

    private:
        const T mRequest;
        bool run(U &response);
};

template<class T, class U>
HttpMethod<T, U>::HttpMethod(const T &request) : mRequest(request)
{
}

// ConcreateStrategyは作らず、template特殊化で呼び出される関数を変える
template<>
bool HttpMethod<HttpGetRequest,HttpGetResponse>::run(HttpGetResponse &response)
{
    std::cout << "GET request header: " << mRequest.header << std::endl;
    response.header="Test: GET";
    response.body="GET body";
    return true;
}

template<>
bool HttpMethod<HttpPostRequest,HttpPostResponse>::run(HttpPostResponse &response)
{
    std::cout << "POST request header: " << mRequest.header << std::endl;
    std::cout << "POST request body: " << mRequest.body << std::endl;
    response.header="Test: POST";
    response.body="POST body";
    return true;
}

//Contextから実行されるかんs
template<class T, class U>
bool HttpMethod<T, U>::request(U &response)
{
   bool res = this->run(response);
   return res;
}

int main()
{
    //通常のstrategy patternだと各メソッドに対応するクラスをインスタンス化することで
    //振る舞いを変えるがtemplateを使う場合はTemplateの型によって振る舞いを変える

    HttpGetRequest getReq{"Test: GET"};
    HttpGetResponse getResp{"",""};

    HttpMethod<HttpGetRequest,HttpGetResponse> httpGet = HttpMethod<HttpGetRequest,HttpGetResponse>(getReq);
    httpGet.request(getResp);
    std::cout << "GET response header: " << getResp.header << std::endl;
    std::cout << "GET response response: " << getResp.body << std::endl;

    HttpPostRequest postReq{"Test: POST","post it"};
    HttpPostResponse postResp{"",""};
    HttpMethod<HttpPostRequest,HttpPostResponse> httpPost = HttpMethod<HttpPostRequest,HttpPostResponse>(postReq);
    httpPost.request(postResp);
    std::cout << "POST response header: " << postResp.header << std::endl;
    std::cout << "POST response body: " << postResp.body << std::endl;
}

実行結果

$ g++ -std=c++11 TemplateStrategy.cpp -o template
$ ./template
GET request header: Test: GET
GET response header: Test: GET
GET response response: GET body
POST request header: Test: POST
POST request body: post it
POST response header: Test: POST
POST response body: POST body

比較用に抽象クラスを用いたテストコードを作る

// Request/Response用の構造体は同じ

// Interfaceにあたるクラス
class HttpMethod
{
    public:
        virtual ~HttpMethod() = default;
        virtual bool request(void* request, void* response) = 0;
};

class HttpGetMethod : public HttpMethod
{
    public:
        bool request(void* request, void* response) override
        {
            HttpGetRequest* getRequest = static_cast<HttpGetRequest*>(request);
            HttpGetResponse* getResponse = static_cast<HttpGetResponse*>(response);

            std::cout << "GET request header: " << getRequest->header << std::endl;
            getResponse->header="Test: GET";
            getResponse->body="GET body";
            return true;
        }
};

class HttpPostMethod : public HttpMethod
{
    public:
        bool request(void* request, void* response) override
        {
            HttpPostRequest* postRequest = static_cast<HttpPostRequest*>(request);
            HttpPostResponse* postResponse = static_cast<HttpPostResponse*>(response);

            std::cout << "POST request header: " << postRequest->header << std::endl;
            std::cout << "POST request body: " << postRequest->body << std::endl;
            postResponse->header="Test: POST";
            postResponse->body="POST body";
            return true;
        }
};
main()
{
    HttpGetRequest getReq{"Test: GET"};
    HttpGetResponse getResp{"",""};
    HttpMethod* httpGet = new HttpGetMethod();
    httpGet->request(&getReq, &getResp);
    std::cout << "GET response header: " << getResp.header << std::endl;
    std::cout << "GET response response: " << getResp.body << std::endl;
    
    HttpPostRequest postReq{"Test: POST","post it"};
    HttpPostResponse postResp{"",""};
    HttpMethod* httpPost = new HttpPostMethod();
    httpGet->request(&postReq, &postResp);
    std::cout << "POST response header: " << postResp.header << std::endl;
    std::cout << "POST response body: " << postResp.body << std::endl;
}

実行結果

$ g++ -std=c++11 NormalStrategy.cpp -o noraml
$ ./noraml
GET request header: Test: GET
GET response header: Test: GET
GET response response: GET body
POST request header: Test: POST
POST request body: post it
POST response header: Test: POST
POST response body: POST body

評価

メモリの比較

$ size template
   text    data     bss     dec     hex filename
   6250     712     280    7242    1c4a template

ROM: 6250+712=6962
RAM: 712+280=992

$ size normal
   text    data     bss     dec     hex filename
   6819     728     464    8011    1f4b normal

ROM: 6819+728=7547
RAM: 728+464=1192

-> Templateを使った方がROMでは585バイト、RAMでは200バイト節約できた

処理時間の比較

シェルから100回実行してみる

$ time for i in {1..100}; do ./template > /dev/null 2>&1; done

real    0m0.752s
user    0m0.172s
sys     0m0.422s
$ time for i in {1..100}; do ./normal > /dev/null 2>&1; done

real    0m0.754s
user    0m0.172s
sys     0m0.297s

-> 実時間はほぼ変わらず、システム時間はむしろTemplateを使った場合のほうが長かった

まとめ

  • strategy patternをC++で実装する際に、サブクラスではなくtemplateを使った場合の例を示した
  • メモリを調べたところ、一応Templateを使った時のほうが節約はできていそう
  • 処理時間はほぼ変わらず
  • ただ正直なところ実装次第でどうにでもなりそうな気もする
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?