メンテナンスをしやすいようにリファクタリングする上で、関数の設計についての個人的な考えを述べます。
####出力値は戻り値としてreturnで返す
例:ポインタ渡しで値を返すのではなく戻り値として返す関数を使うと記述が簡単にできる。
#include <opencv2/opencv.hpp>
#include <stdio.h>
int main(int argc, char* argv[]){
cv::Mat mat = cv::Mat::ones(cv::Size(100, 100), CV_8UC3);
char a[] = "dir1";//単純化のため、既にdir1を作ってあるとします。
char b[] = "file.png";
char b2[] = "file2.png";
char c[1024];
sprintf(c, "%s\\%s", a, b);
cv::imwrite(c, mat);
cv::imwrite(cv::format("%s\\%s", a, b2), mat);
}
OpenCVを例にとり、画像をファイルに保存する例を書いてみた。
1つは、sprintf()を使って出力先のファイル名を作る例、
もう一つは、cv::format()関数を使って出力先のファイル名を作る例です。
cv::format()の使い方は次のページをご覧ください。
[OpenCV-CookBook その他の機能]
(http://opencv.jp/cookbook/opencv_utility.html)
値を返す関数を複数使って1行に書いても、使い慣れた関数である限りは
可読性を損なわず、シンプルに記述できる。
####付記:
MATLAB, Octave, Pythonなどの処理系が使いやすい理由の一つは、関数の戻り値に豊富なデータ構造を取れる点です。
a = func1(b);
と書いたときには、データの依存性は、bからaを生成していることに疑問の余地はありません。
しかし、C/C++系の言語で
func1(a,b);
と書いたときに、
・2つとも入力
・aが入力、bは出力
・aは出力、bが入力
・a,bともに出力の可能性があります。
しかも、悪いことには、入力として参照された後に、値を改変されて
出力値として返ってくる@param[in,out]の可能性があります。
これらが、使いなれない関数では、関数の理解性を損ねるのです。
r = veryLongFunc(a, b, c, d);
と書かれたソースで
/**
* @brief 長くてうんざりするような処理
*/
int veryLongFunc(char* a, char* b, char* c, char*d){
//とてもうんざりする長い記述
int c=someUnfamiliarFunc(a, b);
......
}
などとなったときに、
どの引数が入力で、どの変数が出力なのかを知る方法がないのです。
a = func1(b);
のような簡潔性はなくなってしまいます。
ですから、主張したいのは、「ポインタ渡しで値を返すのではなく戻り値として返す関数を使う」ことです。そのことによってソースコードの可読性は向上します。
・std::vector<>
・std::string
・cv::Mat型
などは、関数の戻り値に使って良いのではないかと考えている範囲です。
####付記:それでも、ポインタ渡し、参照渡しで値を戻したい人に
・職場のコーディングスタイルのために、関数の戻り値に、上記の型が使えない場合、参照渡しを勧めます。
・const属性が可能な引数には、const属性をつけます。
最近のVisual Studioは十分賢く、const属性が無理な引数につけてコンパイルとするとエラーを生じます。そうすると、その関数のcall treeのどこかでその引数の値が改変され戻ってくる可能性があることがわかります。
・Doxygen用のドキュメンテーションコメントに
/**
* @brief 長くてうんざりするような処理
*@param[in] a 説明
*@param[in] b 説明
*@param[out] c 説明
*@param[out] d 説明
*/
int veryLongFunc(const char* a, const char* b, char* c, char*d){
などと書くことも有用です。