2015年12月3日木曜日

【読書ノート】Effective Modern C++ - Item 14

O'Reilly Early Releaseで購入したEffective Modern C++の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 14: 例外を投げない場合はnoexceptで関数を宣言しよう

C++98における例外ととC++11における例外

コンパイラは例外の一貫性の維持を手助けしてくれないため、ある関数が投げる例外に変更が加わると呼び出し元のコードを壊す可能性がある。
開発者が関数が発生しうる例外型を記述し、変更の際は呼び出し元のコードも含めて修正を加えなければならない。
C++98において例外は苦労して使用するに値しないと考えられていた。

C++11ではnoexceptと記述することで関数が例外を出さないことを保証するようになった。
最も意味のある情報はその関数が「例外を投げるのか投げないのか」という0か1の情報だというコンセンサスが(C++11策定時に)得られたため。

noexceptをつけるかどうかはインターフェースデザインの範疇

クライアントコードにおいて関数が例外を投げるかどうかは重要な問題。
例外を投げないのにnoexceptをつけない関数は悪いインターフェースデザイン。
constと同じように例外を投げない関数には常にnoexceptを指定すべき。

コンパイラの最適化とパフォーマンスへの影響

noexceptを指定した場合は例外が発生した場合にstd::terminateでプログラムが強制終了されるようになっていて、コールスタックを管理するオーバーヘッドが削減される。
また、例外が発生して関数から出る時にオブジェクトの生成と逆順に破棄する必要もなくなり、コンパイラの最適化がかかりやすい。

RetType function(params) noexcept; // most optimizable
RetType function(params) throw(); // less optimizable
RetType function(params); // less optimizable

vectorの場合はパフォーマンスに影響する典型的な例。

std::vector<Widget> vw;
Widget w;
vw.push_back(w);

vectorはpush_backする時に容量を超えると別のメモリ領域に要素を丸々コピーしてから古いメモリ領域のオブジェクトを破棄する。
これによって強い例外保証を提供していて、コピーの途中で例外が発生してもvectorの元の状態は保存される。
C++11のムーブセマンティクスを適用する場合は元のデータを書き換えてしまうためこの強い例外保証を脅かす。
しかしmove操作がnoexceptであれば安全であり、moveを利用することでパフォーマンスが改善される。
std::vector::reserveやstd::deque::insertなど別のメモリ領域への割当が発生するメソッドについても同様のことが成り立つ。

条件付きnoexcept

noexceptには条件分岐も可能(true/falseを与えてtrueであればnoexcept)。
noexceptに関数呼び出しを与えるとnoexceptの有無がtrue/falseとして解釈されてnoexceptになるかを判定してくれる。
std::swapは以下の様な実装になっている。

template <class T, size_t N>
void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(*a, *b)));

template <class T1, class T2>
struct pair {
    …
    void swap(pair& p) noexcept(noexcept(swap(p.first, p.first)) && noexcept(swap(p.second, p.second)));
    …
};

swapはSTLの様々なアルゴリズムで使用されているため、例外が発生しないならnoexceptを指定して最適化されることが重要。

noexcept指定の注意点

noexceptを指定することは大きなメリットがあるが、もしnoexceptを指定した関数に修正を加えてnoexceptを指定しないように変更した場合はクライアントコードを壊す可能性がある。
noexceptは可能なら常に指定すべきだが、今後もnoexceptであり続けると考えられる関数に限るべき。
なお、全てのメモリ解放関数とデストラクタは暗黙の内にnoexceptとなっている。デストラクタにnoexcept(false)を明示的に指定すれば例外を発生させられるようになるが通常使うべきではない。

自然な実装で例外が発生しない関数に対してnoexcept指定すべきである。
例外を発生させないがために関数内でtry catchを書くことで複雑になってしまうようでは本末転倒。
パフォーマンス上の利点もtry catch文の追加によるオーバーヘッドでかき消されてしまう。

wide contractsとnarrow contracts

wide contracts: どのような引数を与えても未定義動作にならない
narrow contracts: 前提条件が存在し、その条件に反する引数が与えられると未定義動作になる
narrow contractsである関数では引数が不正な場合に未定義動作となってデバッグが困難となる。
そのような場合には例外で引数が不正であることを呼び出し側に伝えるのが親切。
それを分かっている設計者はwide contractsにはnoexceptをつけ、narrow contractsにはnoexceptをつけないことが多い。

関数が本当にnoexceptであるかはコンパイラがチェックしてくれるわけではない

void setup(); // functions defined elsewhere
void cleanup();
void doWork() noexcept
{
    setup(); // set up work to be done
    … // do the actual work
    cleanup(); // perform cleanup actions
}

setupもcleanupもnoexceptではないのにdoWorkはnoexcept指定されているが、コンパイルは通る。
setupとcleanupはCやC++98のライブラリかもしれない。そもそもstd::strlenなどのCの関数もnoexcept指定されていない。
このような関数が多数存在するので融通を利かせるためにコンパイラはチェックしないようにしている。

2015年10月20日火曜日

【読書ノート】Effective Modern C++ - Item 10

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 10: スコープ無しenumよりもスコープ付きenumを使おう

scoped enumではスコープが有効

一般的な規則では"{"内の変数名はそのスコープ内でしか使えないが、C++98のenumはそのenumがあるスコープ内全体で使えてしまう

enum Color { black, white, red };
auto white = false; // エラー (スコープ内で既に同じ名前が宣言されているため)

C++11のscoped enumなら上述のような「リーク」が起こらない。

enum class Color { black, white, red };
auto white = false; // OK
Color c = white; // エラー (このスコープからは見えない)
Color c = Color::white; // OK
auto c = Color::white; // OK

その宣言の仕方から"enum class"とも呼ばれる。

scoped enumなら意図しない暗黙の型変換を防げる

unscoped enumは数値型に暗黙の型変換がされる。

enum Color {black, white, red};
std::vector<std::size_t> primeFactors(std::size_t x);
Color c = red;
if (c < 14.5) { // doubleと比較
    auto factors = primeFactors(c); // intに変換
}

scoped enumは暗黙の型変換が無い。

enum class Color {black, white, red};
std::vector<std::size_t> primeFactors(std::size_t x);
Color c = red;
if (c < 14.5) { // エラー (Colorからdoubleに変換されない)
    auto factors = primeFactors(c); // エラー (Colorからstd::size_tに変換されない)
}

scoped enumで型変換を行うにはキャストが必要。

if (static_cast<double>(c) < 14.5) {
    auto factors = primeFactors(static_cast<std::size_t>(c));
}

scoped enumは前方宣言が可能

scoped enumなら定義を記述しなくても宣言が可能

enum Color; エラー
enum class Color; // OK

C++98のunscoped enumで前方宣言ができなくなっているのは、定義を見てコンパイラがデータ型を決定しているため。 メモリを最小化するためにコンパイラがenumの定義を見て最小の型を選べるようにしていた。

enum Color { black, white, red }; // コンパイラによってはchar型を選択
enum Status { good = 0,
    failed = 1,
    incomplete = 100,
    corrupt = 200,
    indeterminate = 0xFFFFFFFF
}; // charでは表現できないのでより大きな整数型が選ばれる

前方宣言ができないならenum Statusはヘッダーファイルに書くことになる。 この時enum Statusにaudited = 500を新たに加えるとそのヘッダーファイルをインクルードしているファイルは全てリコンパイルが必要。 しかしscoped enumは前方宣言可能なので、定義を実装ファイルに分離することで無駄なリコンパイルを防げる。

scoped enumのデフォルト型はint型で、他の型にオーバーライドも可能。

enum class Status; // int型
enum class Status: std::uint32_t;
enum class Status: std::uint8_t;

もちろん定義に対しても指定可能。

enum class Status: std::uint32_t {
    good = 0,
    failed = 1,
    incomplete = 100,
    corrupt = 200,
    audited = 500,
    indeterminate = 0xFFFFFFFF
};

C++11ではunscoped enumにもこの記法が使えて、その場合は前方宣言が可能に。

enum Color: std::uint8_t; 

unscoped enumの方が便利な場面と対処法

tupleの1番目の要素を取り出したい時。

using UserInfo = std::tuple<std::string, std::string, std::size_t>;
UserInfo uInfo;
…
auto val = std::get<1>(uInfo);

unscoped enumを使えば即値の1ではなくて定数名を与えることが可能。

enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
…
auto val = std::get<uiEmail>(uInfo);

scoped enumだとくどい書き方になってしまう。

enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
…
auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);

ただし以下のコンパイル時に結果を返す関数を定義すれば

template<typename E>
constexpr auto toUType(E enumerator) noexcept {
    return static_cast<std::underlying_type_t<E>>(enumerator);
}

もう少し短く書くことが可能。

auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

scoped enumには今までに挙げた大きな利点があるため、この場面において少々文字を多く打つことになってもscoped enumの方を使うことに価値がある。

2014年10月26日日曜日

Fabricはexecute APIで自由度の高いデプロイツールに

FabricはPythonの関数を直接呼び出す機能とSSH経由のコマンドを複数のホストに対して楽に発行する機能を持つライブラリです。
簡単な使い方はこちらのページで確認できます。(最近はFabricで検索すると日本語の紹介記事もヒットするようになりました。)
http://docs.fabfile.org/en/latest/tutorial.html

初歩的な使い方だとすぐに使えるようになるのがFabricのメリットです。
また結局はPythonスクリプトなので、サーバー障害時のサービスアウトからパッケージ更新等のデプロイまで幅広く使えます。

しかしある程度大規模で複雑なシステムが対象になると初歩的な使い方では間に合わなくなります。
たとえば、複数のDBサーバをサービスアウトするためには複数のAPサーバーの設定ファイルを書き換える必要がある時はどうすれば良いでしょうか?
あるいは、サービスアウト、デプロイ、サービスインの一連の手順を1台ずつ行いたいという場合はどう記述すれば良いでしょうか?

これらはfab -H [ホスト名] [タスク名]という単純なfab実行方法では不可能で、env.hostsを書き換えるのもタスク実行前に行わなければならないので難しいです。
そこで、fabric.apiのexecuteを使用します。
公式ドキュメントにはこのようなコードが載っています。

from fabric.api import run, execute, task

from mylib import external_datastore

def do_work():
    run("something interesting on a host")

@task
def deploy(lookup_param):
    host_list = external_datastore.query(lookup_param)
    execute(do_work, hosts=host_list)

http://docs.fabfile.org/en/latest/usage/execution.html#intelligently-executing-tasks-with-execute
Using execute with dynamically-set host listsより

host_listに対象となるホストのリストを与えてexecuteすることで、タスクごとに異なるホスト群を指定できることに注目してください。
また、タスク内でこのexecuteは何度も実行できますので、サービスイン・デプロイ・サービスアウトを順に行うタスクも記述可能です。(後述)
これを使えば、1つのタスク内で異なるホスト群に対していろいろなコマンドを発行することができるようになります。
例として、複数台のWebサーバ、Applicationサーバ、Databaseサーバで構成されるシステムについて以下の様なFabricスクリプトを書いてみました。(あくまでFabricのために書いたもので、サーバに対して発行するコマンドはechoのみにしてます。)
ファイル構成は以下の通り(fabfileディレクトリと__init__.pyはデフォルト設定では必須です。)

--fabfile
  |-- __init__.py
  |-- web.py
  |-- ap.py
  |-- db.py
  |-- common.py
  |-- conf.py

この例では各コンポーネントに発行するコマンドがだいたい同じだと想定してComponentという抽象クラスを用意し、各コンポーネントクラスはComponentを継承してservice_in, service_outなどのメソッドをオーバーライドしています。

実行例は以下の通り。(実行結果は長いので省きます)

# webサーバ1台ずつサービスアウト・パッケージ更新・サービスインの一連のタスクを行います
$ fab release:web,update_pkgs
# 障害が起きたwebサーバを1台指定してサービスアウトします
$ fab service_out:web01.example.com
# DBサーバそれぞれに対してバックアップを取ります
$ fab backup_db

__init__.py

# -*- coding: utf-8 -*-

import web, ap, db
from common import get_hosts, get_role
from fabric.api import sudo, execute
from fabric.decorators import task
from functools import partial

components = {
    'web': web.Web(),
    'ap': ap.Application(),
    'db': db.Database()
}

# デプロイ名を指定して各種デプロイを行う関数
def deploy(name):
    if name == 'update_pkgs':
        sudo('echo "Update packages"')
    else:
        raise Exception('Unknown deploy command %s' % name)

# 任意個のデプロイをFabricタスクとして呼び出す関数
def do_deploys(deploys, hosts):
    for d in deploys:
        execute(partial(deploy, d), hosts=hosts)

# 障害時などにホスト名を指定してサービスアウトを行うタスク
@task
def service_out(hostname):
    components[get_role(hostname)].service_out(hostname)

# 障害復旧時などにホスト名を指定してサービスインを行うタスク
@task
def service_in(hostname):
    c = components[get_role(hostname)]
    c.restart(hostname)
    c.service_in(hostname)

# パッケージ更新などのデプロイをコンポーネント指定でまとめて行うタスク
# サービスアウト、デプロイ、プロセス再起動、サービスインを順に行います
@task
def release(target, *deploys):
    c = components[target]
    # 一連のプロセスはサーバ1台毎に行います
    for host in get_hosts(target):
        c.service_out(host)
        do_deploys(deploys, host)
        c.restart(host)
        c.service_in(host)

@task
def backup_db():
    db.Database().backup(get_hosts('db'))

db.py

# -*- coding: utf-8 -*-

from common import Component, get_hosts
from fabric.api import run, execute
from functools import partial

def service_out(hostname):
    run('echo "service out %s"' % hostname)

def service_in(hostname):
    run('echo "service in %s"' % hostname)

def restart():
    run('echo "restart"')

def backup():
    run('echo "Backup database"')

class Database(Component):
    def service_out(self, *hosts):
        for host in hosts:
            execute(partial(service_out, host), hosts=get_hosts('ap'))
    def service_in(self, *hosts):
        for host in hosts:
            execute(partial(service_in, host), hosts=get_hosts('ap'))
    def restart(self, hosts):
        execute(restart, hosts=hosts)
    def backup(self, hosts):
        execute(backup, hosts=hosts)

web.pyとap.pyはdb.pyと同じような内容なので下記を参照
https://github.com/muumu/fabric-sample/blob/master/fabfile/web.py
https://github.com/muumu/fabric-sample/blob/master/fabfile/ap.py

common.py

# -*- coding: utf-8 -*-

import conf
from fabric.api import env
from abc import ABCMeta, abstractmethod

# 各コンポーネントが持っているべきメソッドを宣言します
# 実際のシステムでは全プロセスを止めるstopとかもあるべきかと思います
class Component:
    __metaclass__ = ABCMeta
    @abstractmethod
    def service_out(self, hosts): pass

    @abstractmethod
    def service_in(self, hosts): pass

    @abstractmethod
    def restart(self, hosts): pass

# 大規模システムだとサーバー情報を入れたDBからホスト名を引いてくるかと思いますが
# ここでは簡単のため@rolesデコレータとして利用可能なenv.roledefsを使用します
def get_hosts(role_name):
    if role_name not in env.roledefs:
        raise Exception('Invalid role name: %s' % role_name)
    return env.roledefs[role_name]

# ホスト名からロール名を取得する関数です
# これも簡単のため単にenv.roledefsから逆引き
def get_role(hostname):
    for role, hosts in env.roledefs.items():
        if hostname in hosts:
            return role
    raise Exception('Invalid hostname: %s' % hostname)

conf.py

# -*- coding: utf-8 -*-

from fabric.api import env

env.roledefs = {
    'web': ['web01.example.com', 'web02.example.com'],
    'ap': ['ap01.example.com', 'ap02.example.com'],
    'db': ['db01.example.com', 'db02.example.com']
}

# 1つのホストにタスク実行が終わる毎に切断して接続リソースを節約します
env.eagerly_disconnect = True
# ssh_configの設定内容を使用してssh接続を行います
env.use_ssh_config = True
https://github.com/muumu/fabric-sample
このようにFabricの自由度はかなり高いので、大規模で複雑なシステムに対して柔軟なコマンドを発行したい、という場合に手早く実装できてとても便利です。

2014年10月13日月曜日

Pythonのmap関数をC++で作る試み

Pythonのmapが便利なので、同じように使えそうなmapをC++14で作ってみました。
ソースはこちら。(勉強がてらなので、雑然としてます。)
サンプル: https://github.com/muumu/lib/blob/master/sample/sample_functional.cpp
ライブラリ: https://github.com/muumu/lib/blob/master/functional.h
以下、作ったfn::mapのだいたいの使用感です。

それぞれの要素に関数やメンバ関数を適用したコンテナを作る

{
    vector<string> source = {"a?", "ab?", "abc?", "abcd?"};
    auto output = fn::map(source, fn::init);
    print(output); // {"a","ab","abc","abcd"}
}
{
    list<string> source = {"", "occupied", "", "occupied"};
    auto output = fn::map(source, &string::empty);
    print(output); // {true,false,true,false}
}
{
    array<string, 4> source = {"add", "sub", "mul", "div"};
    auto output = fn::map(source, util::upper);
    print(output); // {"ADD","SUB","MUL","DIV"}
}

違うデータ型になる関数はそのデータ型のコンテナに変換

{
    set<string> source = {"tea", "wine", "milk", "coffee"};
    auto output = fn::map(source, &string::size);
    print(output); // {3,4,6}
}
{
    multiset<string> source = {"tea", "wine", "milk", "coffee"};
    auto output = fn::map(source, &string::size);
    print(output); // {3,4,4,6}
}

キーとバリューを逆転したマップの作成

{
    map<string, int> source_map = {{"RED", 0}, {"GREEN", 1}, {"BLUE", 2}};
    auto reversed_map = fn::map(source_map, fn::swap<string, int>);
    print(reversed_map); // {{0,"RED"},{1,"GREEN"},{2,"BLUE"}}
}

vectorの出力を違うコンテナで受け取る

{
    multimap<string, int> src_map = {{"RED", 0}, {"RED", 0}, {"GREEN", 1}};
    auto func = [](const pair<string, int>& pair) {
        return pair.first + ": " + util::to_string(pair.second);};
    auto result = fn::map(src_map, func);
    print(result); // {"GREEN: 1","RED: 0","RED: 0"}
    set<string> result_set = fn::map(src_map, func);
    print(result_set); // {"GREEN: 1","RED: 0"}
}