5
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?

More than 3 years have passed since last update.

mapやjsonを引数で渡すのをやめた方がいい(c++)

Posted at

##はじめに

学生の頃からプログラムを書いていますが、
当時は自分はわからなかったことが、今になってわかったことがありました。

それは、関数の引数と返り値をしっかりと設計することです。

それについて書いてみようと思います。

タイトルにはmapと書いていますが、mapも引数や返り値にするのを控えたいです。

##jsonを引数、または返り値にしてしまった時の代償

API叩いたり、システムの内部パラメーターとして保存するとき、
よく使うフォーマットがjsonです。

json読み込みの関数を書いてみます。
boostのptreeを使います。

{
  id:"1",
  data:"I am kikochan"
}

### よくない方法


void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
    boost::property_tree::read_json(jsonFile, jsonPtree);
  }

void main(){
//呼び出し
   boost::property_tree::ptree pt;
   read_json_file("test.json",&pt);
   std::string id = pt.get_optional<std::string>("id");
   std::string data = pt.get_optional<std::string>("data");


//また別の処理時に使いたいので呼び出し
//この例では同じ関数内で呼び出しているけど、
//別のファイル、別の処理で呼び出していると仮定してください。
//結果、jsonのキーはなんだっけとか、同じ処理を書いてしまうことが増えてしまいます。
   boost::property_tree::ptree pt;
   read_json_file("test.json",&pt);
   std::string id = pt.get_optional<std::string>("id");
   std::string data = pt.get_optional<std::string>("data");
}

この実装はその時はいいかもしれませんが、
後々、ptreeで値渡しを行なっていると、パッとみた時に、
型がわからなくなるのと、取得する際に都度jsonのキーを入力する必要が出ます。
これは良くないです。

### まだマシな方法


void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
    boost::property_tree::read_json(jsonFile, jsonPtree);
  }

//サブルーチンにして、あちこちから呼び出す。
void getParameter(std::string& id, std::string& data){
   boost::property_tree::ptree pt;
   read_json_file("test.json",&pt);
   id = pt.get_optional<std::string>("id");
   data = pt.get_optional<std::string>("data");
}

void main(){
   std::string id;
   std::string data;
   //jsonのキーの心配はなく、IDEのコード補完も効くから上の例より良いです。
   getParameter(&id,&data);
}

main関数でptreeを取得しパースするのではなく、
一度、間に関数を挟みます。
その関数にptreeの取得とパースを一任する方法です。

こうすると、idとdataは型が決まっているので、IDEのコード補完が効きますし、
都度、jsonから情報を読み込むのにパースの処理を書く必要がありません。

### 一番いい方法


//jsonをクラスとして定義
//コンストラクタ内では、自動的にjsonを読み込みます。
//今回のクラスはjsonの読み込みのみになります。
//jsonのwriteを行いたい場合は、setterを定義し実装してください。
class TestJsonObject{
  std::string id_;
  std::string data_;
  void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
    boost::property_tree::read_json(jsonFile, jsonPtree);
  }

public:
  TestJsonObject(){
    boost::property_tree::ptree pt;
    read_json_file("test.json",pt);
    id_ = *pt.get_optional<std::string>("id");
    data_ = *pt.get_optional<std::string>("data");
  }
  std::string id(){return id_;}//getter
  std::string data(){return data_;}//getter
};

void main(){
  TestJsonObject json;
  std::string id = json.id();
  std::string data = json.data();
}

jsonはデータですので、それを包括するデータクラスたるものを作成することをお勧めします。
そのクラスには、自動的にjsonのデコード、または外部からjson stringを入力した時に、
メンバ変数に格納をする仕組みを実装します。

jsonのパラメータが変更された場合は、そのデータクラスを修正すれば大丈夫ですし、
IDEでももちろんコード補完は行われます。

c++にはデコード、エンコードライブラリはないので手実装しましたが、
SwiftではCodableがありますので、そのようなライブラリがあればそちらを使うことをお勧めします。

最後に

プログラムの実装はなるべく分離度を高くすることが、
効率的な開発に繋がります。

今回は、jsonにフォーカスしての分離の高めかたになります。
少しでもラップしてあげることで、わかりやすくなります。

リストの場合

{
  "id" : "0"
  "lists": [
    {
      "id":"1",
      "data":"I am kikochan"
    },{
      "id":"2",
      "data":"I am kikochan 2"
    }
  ]
}

class TestJsonObject{
public:
  //コンストラクタをprivateにすることで外部からオブジェクトの生成は禁止する。
  //friendでTestJsonObjectはprivateのアクセスを許可する。
  //なので、TestJsonObjectはTestJsonObjectIdDataを作れる
  class TestJsonObjectIdData{
    friend TestJsonObject;
    std::string id_;
    std::string data_;
    TestJsonObjectIdData() = default;
  public:
    std::string id(){return id_;}
    std::string data(){return data_;}
  };
private:
  std::string id_;
  std::vector<TestJsonObjectIdData> lists_;
  void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
    boost::property_tree::read_json(jsonFile, jsonPtree);
  }

public:
  TestJsonObject(){
    boost::property_tree::ptree pt;
    read_json_file("test.json",pt);
    id_ = *pt.get_optional<std::string>("id");

    for (ptree::value_type& list : pt.get_child("lists")) {
      TestJsonObjectIdData testJsonObjectIdData;
      auto info = list.second;
      testJsonObjectIdData.id_ = *info.get_optional<std::string>("id");
      testJsonObjectIdData.data_ = *info.get_optional<std::string>("data");
      lists_.push_back(testJsonObjectIdData);
    }
  }
  std::string id(){return id_;}
  std::vector<TestJsonObjectIdData> lists(){return lists_;}
};


int main(){
    TestJsonObject testJsonObject;
    auto k = testJsonObject.lists();
    std::cout << testJsonObject.id() << std::endl;
    for(auto &&s : k){
      std::cout << s.id() << std::endl;
      std::cout << s.data() << std::endl;
    }
}
5
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
5
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?