Modern C++チャレンジを購入
O'REILLYから久し振りのC++関連の書籍「Modern C++チャレンジ」が出ていたので購入してみました。
タイトルにModernと入っていますが、本書ではC++17も対象になっています。比較的新しい項目を扱っていた「Effective Modern C++」でもC++11/14が対象だったので(こちらは2015年の本ですが)、C++17について書いてある書籍が出たというのはありがたいことです。
ただ、本書は言語仕様の一方的な解説書ではありません。サブタイトルに「C++17プログラミング力を鍛える100問」とあるように、問題/解答が記載された問題集形式となっています。問題の分野は最大公約数等、数学的な問題を扱うものから文字列や正規表現を扱うもの。果ては「アーカイブ、画像、データベース」とかなり大きめの分野を扱うなど多岐に渡ります。
解説書というわけではないので、この本を読んだからといってC++17に詳しくなれるわけではありません。C++17に関連しない問題もたくさんあります。「C++17」部分に過剰な期待をせず、2018,2019年における現代的なコーディングをした場合、どのように書けるかといったことを学ぶにはいいと思います。
また、問題集形式なので実際に手を動かした方がいいと思います。一つ一つの問題は小さく、すぐに書けるものが多いと思います。小さいプログラムだからこそ、自分の書いたものと解答のコードの違いを比べやすく勉強になることも多いと思います。
後半になると扱う分野も大きくなってくるので(例えば10章 アーカイブ、画像、データベース)、解答でも外部ライブラリを使うだけでCookbook的な側面が強くなってきますが、幾つかの有名なライブラリが紹介されており、自分であまり扱ったことがない分野で将来触れることになった場合は役に立つかもしれません。
実際に一つ問題をやってみます。
問題26 定した区切り文字で文字列を連結する
一連の文字列と区切り文字を入力として、すべての入力文字列を区切り文字を介して連結した新しい文字列を出力する関数を書きなさい。区切り文字は最後の文字列のあとには付きません。入力文字列がなければ、空文字列を返します。
書籍中より引用
入力 {"this","is","an","example"} 区切り文字 ' '(空白) 出力 "this is an example"
文字列をjoinする問題ですね。いくつか書いてみましょう。
以下は、std::vector<std::string>で渡された文字列を愚直に結合していくパターンです。{"foo", "bar"}のように初期化子リストを直接引数に渡すこともできます。
join.cc
#include <iostream>
#include <string>
#include <vector>
#include "test.h"
std::string join(const char* delimiter, const std::vector<std::string>& strings) {
std::string joined;
if (!strings.empty()) {
joined = strings[0];
for (std::size_t i = 1 ; i < strings.size() ; i++) {
joined += delimiter;
joined += strings[i];
}
}
return joined;
}
int main() {
// 初期化子リストで指定
test("foo,bar", join(",", {"foo", "bar"}));
test("foo", join(",", {"foo"}));
test("", join(",", {}));
test("", join(",", {""}));
// std::vectorで指定
std::vector<std::string> vec = {"foo", "bar"};
test("foo,bar", join(",", vec));
}
なお、test()関数は別ファイル(test.cc,test.h)にわけています。
test.h
#ifndef TEST_H #define TEST_H #include <iosfwd> #include <string> extern void test(const char* expected, const std::string& result); #endif
test.cc
#include "test.h"
#include <iostream>
void test(const char* expected, const std::string& result) {
std::cout << result << " ";
if (expected == result) {
std::cout << "OK";
} else {
std::cout << "NG (expected:" << expected << ")";
}
std::cout << std::endl;
}
今度は、std::ostream_iteratorを使って書いてみます。文字列結合処理の例としてよくあげられているパターンですね。先ほどよりは少し格好良くなっています。
join2.cc
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <sstream>
#include <iterator>
#include "test.h"
// std::ostream_iteratorを使うパターン
std::string join(const char* delimiter, const std::vector<std::string>& strings) {
std::ostringstream os;
if (!strings.empty()) {
std::copy(strings.cbegin(), strings.cend() - 1, std::ostream_iterator<std::string>(os, ","));
os << *(strings.end() - 1);
}
return os.str();
}
int main() {
// 初期化子リストで指定
test("foo,bar", join(",", {"foo", "bar"}));
test("foo", join(",", {"foo"}));
test("", join(",", {}));
test("", join(",", {""}));
// std::vectorで指定
std::vector<std::string> vec = {"foo", "bar"};
test("foo,bar", join(",", vec));
}
最後に、join2.ccでは引数に初期化子リストかstd::vectorしか渡せないので、std::arrayやstd::list等の他のコンテナも渡せるようにしてみます。
join3.cc
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <list>
#include <sstream>
#include <iterator>
#include <initializer_list>
#include "test.h"
// array等、他のコンテナでも渡せるようにする
// list等の双方向イテレータのコンテナも使えるようにする
template <class Container>
std::string join(const char* delimiter, const Container& c) {
std::ostringstream os;
if (!c.empty()) {
auto last = std::prev(c.cend());
std::copy(c.cbegin(), last, std::ostream_iterator<std::string>(os, ","));
os << *last;
}
return os.str();
}
// 初期化子リストも受け付けられるように
std::string join(const char* delimiter, std::initializer_list<std::string> strings) {
return join(delimiter, std::vector<std::string>(strings.begin(), strings.end()));
}
int main() {
// 初期化子リストで指定
test("foo,bar", join(",", {"foo", "bar"}));
test("foo", join(",", {"foo"}));
test("", join(",", {}));
test("", join(",", {""}));
// std::vectorで指定
std::vector<std::string> vec = {"foo", "bar"};
test("foo,bar", join(",", vec));
// std::arrayで指定(ランダムアクセスイテレータ)
std::array<std::string, 2> arr = {"foo", "bar"};
test("foo,bar", join(",", arr));
// std::listで指定(双方向イテレータ)
std::list<std::string> list = {"foo", "bar"};
test("foo,bar", join(",", list));
}
これで、ランダムアクセスイテレータを扱うコンテナ(std::vector,std::array等)や双方向イテレータを扱うコンテナ(std::list等)も引数として渡せるようになりました。たかがjoinですが、いろいろな書き方ができるのがわかります。
書籍での解答は、、、実際に買って確認してください。
この本は、オライリーのEStoreで購入しました。 オライリー本は最近はEStoreで購入することが多いのですが、PDFでどこでも読めるし、変なDRMもかかっていない。再ダウンロードも可能ということで便利で助かっています。オライリー本を本棚に詰め込みすぎると棚板も歪んでしまいますしね。
投稿日:2019/04/02 16:17