2015-09-24

江添ボドゲ会@10月の開催案内

江添ボドゲ会@10月 - connpass

江添ボドゲ会@9月は盛況で終わったので、10月も開催する。

10月はちょうど誕生日の24日が土曜日で都合がいい。

場所は例の通り江東区塩浜にある江添宅

Goole Map: 江添宅

最寄り駅は東西線木場駅、都営バスならば業10か錦13が塩浜二丁目に止まります。

木場駅4b出口から出て南下。イトーヨーカドーを通り過ぎ、中央自動車学校と歩道橋がある塩浜通りで東に進み、サンクスとすき家に挟まれた道を南下し、木場リサイクルの向かい側のマンションの一階の101号室。

入り口のインターホンで101号室を呼び出してください。

2015-09-18

memory_resourceについて

N4529: C++ Extensions for Library Fundamentals, Version 2, Working Draftでは、Library Fundamentalsという標準ライブラリに対する拡張が規定されている。これは、いずれC++規格に取り入れられる予定である。

さて、この拡張提案の中で、メモリ確保に新しいライブラリが加わった。memory_resourceだ。

メモリーリソースとは、アロケーターと同じように、メモリーの確保と解放をするためのインターフェースだ。アロケーターがテンプレートで渡されることを前提とした満たすべき要件となっているのに対し、メモリーリソースは、abstractクラスとなっている。

class memory_resource {

public:
  virtual ~memory_resource();

  void* allocate(size_t bytes, size_t alignment = max_align);
  void deallocate(void* p, size_t bytes,
                  size_t alignment = max_align);

  bool is_equal(const memory_resource& other) const noexcept;

protected:
  virtual void* do_allocate(size_t bytes, size_t alignment) = 0;
  virtual void do_deallocate(void* p, size_t bytes,
                             size_t alignment) = 0;

  virtual bool do_is_equal(const memory_resource& other) const noexcept = 0;
};

memory_resourceは、publicなメンバーであるallocate, deallocate, is_equalを使う。これらは、同名のdo_foobarを呼び出すようになっている。memory_resourceはabstractクラスなので、実際のストレージ確保は、memory_resourceを基本クラスとしたクラスで実装する。

allocateでストレージを確保し、deallocateでストレージを解放する。is_equalは、thisでallocateしたポインターがotherのdeallocateに渡すことと、その逆に、otherでallocateしたポインターがthisのdeallocateに渡すこととが、両方ともできる場合に、trueを返す。

例えば、::operator newとdeleteを使う単純なメモリーリソースは以下のように実装できる

#include <experimental/memory_resource>

class new_delete_resource : public std::experimental::pmr::memory_resource
{
protected :
    void * do_allocate( std::size_t bytes, size_t alignment )
    {
        return ::operator new( bytes ) ;
    }

    void do_deallocate( void* p, std::size_t bytes, std::size_t alignment )
    {
        ::operator delete( p ) ;
    }

    // 常にtrue
    bool do_is_equal( memory_resource const &amp; )
    {
        return true ; 
    }
} ;

このようにして作ったメモリーリソースは、メモリーリソースのインターフェースをアロケーターのインターフェースにあわせるラッパー、polymorhipc_allocator<T>に渡すことで、アロケーターとして使えるようになる。

#include <experimental/memory_resource>

template < typename T >
using pa = std::experimental::pmr::polymorphic_allocator<T> ;

template < typename T >
using vec = std::vector< T, std::experimental::pmr::polymorphic_allocator<T> > ;

int main()
{
    auto mr = std::make_unique<new_delete_resource>() ;

    pa<int> a( mr.get() ) ;
    vec<int> v( a ) ;
}

このnew_delete_resourceのようなメモリーリソースへのポインターを返すnew_delete_resource関数がLibrary Fundamentalsに入っている。また、get/set_default_resourceという関数もある。

Library Fundamentalsでは、2つのメモリーリソースを標準で提供している

プールリソース

プールリソースとはメモリー確保を、キリのいい種類のブロックサイズごとに区切って(例えば8, 16, 32, 64バイト...など)、ブロックサイズごとにプールと呼ばれるメモリーの「チャンク」を管理して、そのプールからメモリーを割り当てる戦略を取るメモリーリソースだ。

synchronized_pool_resourceとunsynchronized_pool_resourceとがある。違いは、複数のスレッドから同期処理を行わずにアクセスできるかどうかだ。

int main()
{
    std::experimental::pmr::synchronized_pool_resource pool ;
    std::experimental::pmr::polymorphic_allocator<int> poly(pool) ;

    vec<int> v( poly ) ;
}

プールリソースは、ブロックサイズの上限を超えるサイズのストレージを確保する際は、あらかじめ上流メモリーリソース(Upstream memory resource)を使う。上流メモリーリソースは、デフォルトではデフォルトリソース(get_default_resource)が使われる。自前のメモリーリソースを使わせたい場合は、コンストラクターで指定できる。


std::experimental::pmr::synchronized_pool_resource
    pool( custom_resource{} ) ;

また、pool_options型の引数をコンストラクターに渡すと、ブロックサイズの上限を指定できる。pool_options型は以下のように定義されている。

struct pool_options {
  size_t max_blocks_per_chunk = 0;
  size_t largest_required_pool_block = 0;
};

max_blocks_per_chunkは、上流メモリーリソースから一度に確保するブロックの数を指定する。0の場合や、実装の上限を上回る場合は、実装の上限が使われる。

largest_required_pool_blockは、プールする最大のブロックサイズを指定する。0の場合や、実装の上限を上回る場合は、実装の上限が使われる。

モノトニックバッファーリソース

monotonic_buffer_resourceは、限定的な条件で、とても高速なメモリ確保と解放を行うメモリーリソースだ。

monotonic_buffer_resourceのallocateは上流メモリーリソースから十分な大きさのストレージを確保して、必要なサイズに切り分けて返す。deallocateは、何もしない。つまり、確保されたストレージは、monotonic_buffer_resourceのオブジェクトが生存する間、開放されることがない。そのため、メモリ使用量はmonotonicに増え続ける。

使用済みオブジェクトをすべて一括して解放したい場合にきわめて効率的に動く。

monotonic_buffer_resourceは、単一のスレッドからアクセスすることを想定している。allocateとdeallocateは同期しない。

コンストラクターは、上流メモリーリソースと、確保するストレージの初期サイズを指定することができる。

std::experimental::pmr::monotonic_buffer_resource( 10 * 1014 * 1024, get_default_resource() ) ;

メンバー関数にはreleaseがあり、呼び出すと今まで確保したストレージをすべて解放する。デストラクターはreleaseを呼び出す。

polymorphic_allocatorは、実行時ポリモーフィズムにより、アロケーターの挙動を実行時に変えるメモリーリソースのインターフェースを、既存のアロケーターのインターフェースにすりあわせるためのラッパーである。つまり、メモリーリソースは実行時に変えることができる。

int main()
{
    std::string input
    std::cin >> input ;

    std::experimental::pmr::memory_resource * ptr = get_default_resource ;

    std::unique_ptr< memory_resource > raii ;

    if ( input == "pool" )
    {
        raii.reset( new std::experimental::pmr::synchronized_pool_resource() ) ;
        ptr = raii.get() ;
    }
    else if( input == "monotonic" )
    {
        raii.reset( new std::experimental::pmr::monotonic_resource() ) ;
        ptr = raii.get() ;
    }

    std::experimental::pmr::polymorphic_allocator<int> poly( ptr ) ;

    std::vec< int > v( poly ) ;

}

アロケーターを実行時に決める機能はなかなか便利だ。既存のアロケーターにも欲しい。そのため、Library Fundamentalsには、既存のアロケーターのインターフェースをメモリーリソースのインターフェースにすりあわせるラッパーも用意されている。

std::experimental::pmr::resource_adaptor< std::allocator<char> > adaptor ;
std::experimental::pmr::memory_resource * ptr = &adaptor ;

ただし、アロケーターはすべての特殊化がcharにrebindできる制約を満たさなければならない。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。9月25日に筆者の書いた「C++11/14コア言語」がアスキードワンゴから出版される。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

Effective Modern C++の見本が届いた

Effective Modern C++の見本が届いた。

なぜ私のもとに見本が送られてきたかというと、知人の岡田真太郎君が査読をしたのだという。

中を見てみたが、C++11の新機能のさわりの解説と、いくつかの落とし穴の照会と、落とし穴を避けるためのいくつかのお作法が含まれているようだ。

最後の方の並列APIの章は、なんだかまとまりが無いように感じた。threadのことは一切解説しないのにthreadはjoin可能な状態で放置するな(デストラクターでstd::terminateが走るため)というお作法だけ書くのはよくわからない。また、atomic操作のさわりだけ教えても意味がないように思う。

weak_ptrも何故紹介したのかわからない。weak_ptrとatomic操作は、素人が付け焼き刃で使える機能ではない。

この本の浅い解説から考えると、大半の読者はweak_ptrやatomicを使う必要に迫られることはまずないし、使う資格もないだろうと思う。このように浅く存在だけ解説すると、必要もないのに使ってみたくなる人間が増えはすまいか。心配になる。

とはいえ、考えてみると、この本は初心者を対象にしたものではない。この本を読むには、すでにC++をある程度実用的に書ける必要がある。かつ、Effective C++やHerb SutterのExceptional C++などのC++本を読んでいて、C++03時代のC++は知っていることを前提にしている。

また、そもそも、今C++を書く人間は、到底素人ではないからこれでいいのだという意見も聞いた。

2015-09-14

C++のアロケーター

次のC++標準化委員会の文書が出るまで、アロケーターのことでも書くことにした。

アロケーターとは、ストレージを確保するためのライブラリだ。標準ライブラリには、std::allocator<T>がある。

#include <memory>

int main()
{
    std::allocator< std::string > a ;
    auto raw_ptr = a.allocate( 1 ) ; // std::string一つ分のストレージを確保
    a.deallocate( raw_ptr, 1 ) ; // 解放、確保した個数を引数に渡す
}

独自の方法でストレージの確保をしたい場合、独自のアロケーター要求を満たすクラスを実装して、アロケーターをサポートしているライブラリに渡せばよい。

class custom_allocator ;

int main()
{
    // デフォルト実引数でcustom_allocatorがデフォルト初期化されて渡される    
    std::vector<int, CustomAllocator> v( ) ; 
}

アロケーターをわざわざ書くのは面倒なので、C++11では、allocator_traits<T>が導入された。これは、以下のように使うライブラリだ。

int main()
{
    std::allocator< std::string > a ;
    using t = std::allocator_traits< decltype(a) > ;

    // ストレージ確保
    auto ptr = t::allocate( a, 1 ) ;
    // ストレージ上にオブジェクトを構築
    // Variadic Templatesにより第三引数以下に構築時の実引数を渡せる
    t::construct( a, ptr, "hello" ) ;
    // オブジェクトを破棄
    t::destroy( a, ptr ) ;
    // ストレージ解放
    t::deallocate( a, ptr, 1 ) ;
}

allocator_traitsは、テンプレート実引数にアロケーターを指定すると、そのアロケーターのラッパーとして働く。これにより、独自のアロケーターにネストされた型として、pointerだのconst_pointerだのvoid_ponterだのといった、今まで要求されていた様々なメンバーを実装する必要がなくなった。

また、クラス型でさえあれば何でも指定することができる。アロケータークラスでストレージ確保を実装するのではなく、allocator_traitsの特殊化で実装することも可能だ。

class empty ;

template < >
struct std::allocator_traits< empty >
{
// 実装
} ;

C++11からの標準ライブラリは、アロケーターを直接使わず、allocator_traitsを介して使うよう規定されている。

C++11では、この他にもアロケーター自身に機能追加が行われた。

コンテナのオブジェクトをコピー/ムーブするとき、アロケーターオブジェクトもコピー/ムーブされる。この際のアロケーターのコピー/ムーブの方法を制御できる機能だ。

std::vector<int> a ;
std::vector<int> b = a ; // アロケーターがコピー構築される
b = a ; // アロケーターがコピー代入される
std::vector<int> c = std::move(b) ; // アロケーターがムーブ構築される
b = std::move(c) ; // アロケーターがムーブ代入される

コピー/ムーブと、構築/代入の組み合わせの4種類について、その方法を選べる機能が追加された。

template < typename Allocator >
class Container
{
    Allocator a ;
    using t = std::allocator_traits<Allocator> ;
public :
    // デフォルト初期化
    Container( Allocator & alloc = Allocator() )
        : a( alloc ) { }
} ;

このようなコンテナーがあるとする。

select_on_container_copy_construction

コンテナーがコピー構築されるときは、必ずこの関数が呼ばれる。具体的には、コピーコンストラクターが以下のような実装になる。

template < typename T >
Container::Container( Container const & ref )
    : a( t::t::select_on_container_copy_construction(ref.a) )
{ }

アロケーターは、自分自身を返すか、あるいは別のオブジェクトを返すかを選ぶことができる。

これにより、コピーできないアロケーターを使ったコンテナーでもコピーできるようになる。

propagate_on_container_copy_assignment
propagate_on_container_move_assignment
propagate_on_container_swap

コンテナーをコピー代入、ムーブ代入、swapするときは、アロケーターのオブジェクトも変更される。上の3つの名前のメンバーがアロケーターでstd::false_typeと定義されている場合は、アロケーターは変更しない。

class no_propagate_allocator
{
public :
    using propagate_on_container_copy_assignment = std::false_type ;
    using propagate_on_container_move_assignment = std::false_type ;
    using propagate_on_container_swap = std::false_type ;

    // その他の実装
} ;

template < typename T >
using vec = std::vector<T, no_propagate_allocator > ;

int main()
{
    std::vector<int> a, b ;
    a = b ; // aのアロケーターオブジェクトは変更される。

    vec<int> c, d ;

    c = d ; // cのアロケーターオブジェクトは変更されない。   
} ;

is_always_equal

すべてのアロケーターのオブジェクトが等しい場合、std::true_typeと定義される。たとえば、std::allocatorのis_always_euqalはstd::true_typeである。

これは、アロケータークラスが何らかの異なると比較される状態を持っているかどうかを調べるのに使える。

template < typename Allocator >
void f()
{
    using t = std::allocator_traits<Allocator> ;
    bool b = t::is_always_equal() ; // true
}

allocator_arg

allocator_argは以下のように定義されている。

namespace std {
    struct allocator_arg_t { };
    constexpr allocator_arg_t allocator_arg{};
}

allocator_argは単なるタグとして型情報を利用するためだけのクラスだ。これは一部のコンテナーのオーバーロード解決に使われる。

たとえば、std::tupleは、アロケーターをサポートしている。しかし、tuple自体にアロケーターを渡したい場合もある。

int main()
{
    std::tuple< std::allocator<int> > t( std::allocator<int>{} ) ;
}

オーバーロード解決のために、tupleにアロケーターを渡すときは、allocator_argを渡す。

int main()
{
    std::tuple< std::allocator<int> > t( std::allocator_arg, custom_allocator, std::allocator<int>{} ) ;
}

現在、標準ライブラリでは、std::tuple, std::function, std::promise, std::packaged_taskがstd::allocator_arg_tを引数に取るオーバーロードを提供している。

uses_allocator<T, Alloc> traits

uses_allocator traitsはstd::true_typeかstd::false_typeから派生している。std::true_typeから派生している場合、コンテナーの要素型はコンテナーに渡されたアロケーターのオブジェクトを使って初期化される。例えば、std::queueやstd::stackに渡したアロケーターオブジェクトが、内部のコンテナー型のアロケーターの初期化にも使われる。

scoped_allocator_adaptor

scoped_allocator_adaptorは、コンテナーの要素型のアロケーターまで、アロケーターオブジェクトを指定させる目的で使う。

たとえば、あるアロケータークラスは、オブジェクトごとに内部で状態(ヒープメモリ等)を持っている。

class allocator
{
    std::shared_ptr<heap_area> ptr ;
// その他
} ;

メモリ確保はこのアロケーターのオブジェクトを経由して行わせたい。あるコンテナーに、このアロケーターのあるオブジェクトを使わせたい場合、つまり単一のヒープ領域からストレージを確保させたいは、以下のようにアロケーターを指定すれば事足りるだろうか。

class allocator ;

int main()
{
    allocator a ;
    std::vector< std::string, allocator > v(a) ;
    v.push_back( std::string("hello") ) ;
}

こうすれば、外側のvectorは確かにallocatorを使ってくれるものの、内側のstring(std::basic_string<char, std::char_traits<char>,. std::allocator<char> >)は、依然としてstd::allocatorを使う。

とすれば、以下のようにすべきだろうか。

using str = std::basic_string< char, std::char_traits<char>, allocator > ;
using vec = std::vector< str, allocator > ;

int main()
{
    allocator a ;
    vec v(a) ;
    // 同じヒープ領域から確保させるためには、同じアロケーターのオブジェクトからコピーさせる必要がある。
    v.emplace("hello", a ) ;
}

これは動くが面倒だ。特に、コンテナーのネストが増えるともっと面倒になる。

using str = std::basic_string< char, std::char_traits<char>, allocator > ;
using str = std::basic_string< char, std::char_traits<char>, allocator > ;
template < typename T >
using vec = std::vector< T, allocator > ;

int main()
{
    allocator a ;
    vec< vec<str> > v( a ) ;
    v.emplace( a ) ;
    v[0].emplace( "hello", a ) ;
}

scoped_allocator_adaptorは、アロケーターをラップして、このような内側のコンテナーへのアロケーターに同じオブジェクトを使いまわせる。

というのが、現行の規格の文面とN2554を読む限りのscoped_allocator_adaptorの機能だと思うのだが、どうもうまく動いていない。というのも、内側のコンテナーなり要素なりを外側のコンテナーに追加するとき、insertにしろemplaceにしろ、やはり内側のコンテナーにアロケーターオブジェクトを手動で渡さなければならないことには変わりがないので、scoped_allocator_adaptorを使う利点が全く理解できない。世の中に転がっているサンプルは全部間違っているように思われる。一体どういうことなのだろうか。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-09-13

C++の規格におけるcontextually converted to boolという用語について

C++の規格には、"contextually converted to bool"という用語が出てくる。これは一体どういう意味なのか。

まず、"implicitly converted to a type T"という用語がある。これは、ある型Eの式eがあるとき、eをある型Tのオブジェクトtに暗黙に変換できるかどうかという意味で、

T t = e ;

が合法かどうかで決まる。

たとえば、型Eがクラスで、型Tに対する非explicit変換関数を持つ場合、EはTに暗黙に変換できる。

ところが、conditionと呼ばれる一部の文脈(ifやwhileやfor、!や&&や||、条件演算子、static_assert, noexcept)では、explicit変換関数であっても、見かけ上暗黙に変換できる。

struct X
{
    explicit operator bool() { return true ; } 
} ;

int main()
{
    X x ;
    if ( x ) { } // OK、boolに変換できる
}

これは、conditionが、contextually converted to boolという用語を使っているからだ。これを"contextually converted to bool"といい、以下の式が合法かどうかで判断される。


bool t(e) ;

そのため、boolへのexplicit変換関数を持つクラス型でもboolに変換できる。

もうひとつ、規格には"contextually implicitly converted to"(...a specified type T)という用語がある、 これは、今のところは整数定数式とswitch文だけで使われている用語で、そこでは整数型やunscoped enum型に暗黙に唯一の変換ができることが条件となっている。こちらは、explicit変換関数ではだめだ。

struct X { operator int() { return 0 ; } operator long () { return 0l ;} } ; int main() { X x ; // エラー、複数の整数型への変換関数がある switch( x ) { } }

ドワンゴ広告

この記事はドワンゴ勤務中に書かれなかった。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-09-07

歌舞伎座.tech 番外編「C++11/14コア言語」出版記念 開催のお知らせ

歌舞伎座.tech 番外編「C++11/14コア言語」出版記念 - connpass

筆者の拙著、「C++11/14コア言語」が2015年9月25日にアスキードワンゴから出版されることを記念して、9月30日に、ドワンゴの入っている歌舞伎座タワーの14F、セミナールームで軽い勉強会を行う。

当日、本を持って行くと筆者がオートグラフを書き込むことになっている。また、当日、少数だが現地で本の販売も行う。

勉強会であるので、当日は一般からの発表も受け付けている。

内容自体は、すでにGitHubで公開している。

EzoeRyou/cpp-book

EzoeRyou/cpp14-appendix

ドワンゴ広告

気がついたらドワンゴに雇われていたが、本まで出すことになった。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2015-09-01

江添ボドゲ会@9月開催の案内

江添ボドゲ会@9月 - connpass

9月の江添ボドゲ会は20日の日曜日に開催します。

江東区塩浜2-14-2 アライハウスⅡ 101号室

最寄り駅は東西線木場駅。都営バスならば業10か錦13が塩浜二丁目に止まります。

木場駅4b出口から出て南下。イトーヨーカドーを通り過ぎ、中央自動車学校と歩道橋がある塩浜通りで東に進み、サンクスとすき家に挟まれた道を南下し、木場リサイクルの向かい側のマンションの一階の101号室。

入り口のインターホンで101号室を呼び出してください。