背景
- 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を使った時のほうが節約はできていそう
- 処理時間はほぼ変わらず
- ただ正直なところ実装次第でどうにでもなりそうな気もする