本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

C++標準化委員会の文書: P0460R0-P0469R0

[PDF] P0460R0: Flat containers wording

連続したストレージ上に構築したソート済みのデータ構造を持つ連想コンテナー、flat_map/flat_setの文面案。

入るべきだ。

[PDF] P0461R0: Proposed RCU C++ API

物理ページを仮想2ページに分割するクソみたいなオナニーレイアウトを使用したゴミ文書。

内容はRCUライブラリのようだがレイアウトがクソすぎて詳しく読む気がしない。

[PDF] P0462R0: Marking memory order consume Dependency Chains

memory_order_consumeの依存チェインについてLinusが激怒したことが発端の議論の落とし所。

これまたクソみたいなオナニーレイアウトを使用しているため読みづらい。

P0463R0: endian

コンパイル時にエンディアンを取得するこれ以上ないくらいわかりやすいライブラリの提供。<type_traits>に以下の定義が追加される。実装例は以下の通り。


enum class endian
{
    little = __ORDER_LITTLE_ENDIAN__,
    big    = __ORDER_BIG_ENDIAN__,
    native = __BYTE_ORDER__
};

nativeは実装のエンディアンになる。

コンパイラーはバイトオーダーを知っているし、バイトオーダーはコンパイル時に取得できる。

世の中にはバイトオーダーを実行時に変更できるCPUが存在するが、バイトオーダーの実行時の変更に耐えるOSは存在しない。バイトオーダーを変更する関数は提案されていないが、これは提案しない。PDPエンディアンが存在しないが、現在、PDPをターゲットにしたC++14コンパイラーは存在しない。将来、全く新しいエンディアンに対応する必要が生じた場合でも、この設計ならば容易に対応可能である。

P0464R0: Revisiting the meaning of foo(ConceptName,ConceptName)

現在のコンセプト提案では、

R f( ConceptName a, ConceptName b ) ;

という関数宣言は、

template < Conceptname C >
R f( C a, C b ) ;

と書いたものと同等になるが、これを、

template < Coceptname C1, ConceptName C2 >
R f( C1 a, C2 b ) ;

と同等にしようと言う提案。

これは当然こうなるべきだ。

[PDF] P0465R0: Procedural Function Interfaces

一応読んだが、なんとも一言でまとめがたい提案だ。一種の契約型プログラミングなのだろうか。それにしても、コードが冗長になり、しかも関数という単位が断片化され、非常に人間の目によって処理が追いにくくなるのではないか。

[PDF] P0466R0: Layout-compatibility and Pointer-interconvertibility Traits

2つの型がレイアウト互換かどうかを調べるtraits, are_layout_compotible<T, U>の追加

レイアウト互換とは、例えば以下のような型だ。

struct A { int x, y ; } ;
struct B { int x, y ; } ;

void f( A * a )
{
    B * b = reinterpret_cast<B *>(a) ;
}

このようなコードは必要になる。問題は、プログラマーがいかに注意深くレイアウト互換に気をつけようと、後でクラスの定義が変更されてレイアウト互換が壊れた場合、このコードはコンパイル時にエラーにならず、実行時に不可解なエラーとなる。ところで、コンパイラーは型がレイアウト互換であるかどうかを知っている。ならば、型がレイアウト互換であるかどうかを返すtraitsがあれば、このようなコードはコンパイル時に検証できる。

struct A { int x, y ; } ;
struct B { int x, y ; } ;

void f( A * a )
{
    static_assert( are_layout_compatible_v<A, B> ) ;
    B * b = reinterpret_cast<B *>(a) ;
}

これは便利なtraitsだ。

この提案は他にも、以下のようなtraitsを提案している。

is_initial_base<base, derived>

baseがderivedの最初の基本クラスである場合にtrueを返すtraits

struct b1 { int data ; } ;
struct b2 { int data ; } ;

struct d1 : b1, b2 { } ;
struct d2 : b2, b1 { } ;

// true
constexpr bool a = is_initial_base_v<b1, d1> ;
// false
constexpr bool b = is_initial_base_v<b1, d2> ;
template <class S, class M>
constexpr bool is_initial_member( M S::*m ) noexcept;

Sが標準レイアウトクラス型で、Sがunion型か、mがSの最初の非staticデータメンバーである場合にtrueを返す。

struct X { int x, y ; } ;
union Y { int x ; short y ; } ;
int main()
{
    // true
    is_initial_member( &X::x ) ; 
    // false
    is_initial_member( &X::y ) ;

    // true
    is_initial_member( &Y::x ) ;
}
template <class S1, class M1, class S2, class M2>
constexpr bool
are_common_members( M1 S1::*m1, M2 S2::*m2 ) noexcept;

S1とS2が標準レイアウト型で、m1とm2がそれぞれS1とS2のレイアウト内で共通のオフセットから始まる場合にtrueを返す。

struct S1
{
    int x ;
    int y ;
} ;

struct S2
{
    int x ;
    char y[sizeof(int)] ;
} ;

// true
are_common_members( &S1::y, &S2::y ) ;

あれば便利だろう。

P0467R0: Iterator Concerns for Parallel Algorithms

並列アルゴリズムは、既存のアルゴリズムのオーバーロードという形で追加された。既存のアルゴリズムのイテレーターの要件はあまりにも弱すぎて、並列アルゴリズムで使うには問題がある。

入力イテレーターと出力イテレーターは、値に対する具体的なオブジェクトがあるとは限らず、かつマルチパス保証(複数回イテレートして結果が同じこと)がない。並列アルゴリズムで入力イテレーターから入力を得るには、まず入力の個数分のメモリを確保して入力を全部コピーしなければならない。並列アルゴリズムで出力アルゴリズムに順番に出力するには、先頭から出力するよう同期処理が必要だ。

このため、並列アルゴリズムのイテレーターの要件を前方イテレーターに引き上げる。将来的に、イテレーターの要件を引き下げることができる制約や実装が示されたならば、要件を引き下げる。

P0468R0: P0468R0 : An Intrusive Smart Pointer

intrusive reference countingを採用したスマートポインター、retain_ptrの提案。

現在のshared_ptr<T>は、Tへのポインター型とリファレンスカウントのための整数型を別々に確保して管理している。

template < typename T >
class shared_ptr
{
    T * ptr ;
    long * count
public :
    explicit shared_ptr( T * ptr )
        : ptr( ptr ), count( new long{1} )
    { }
    shared_ptr( shared_ptr other )
        : ptr( other.ptr ), count( other.count )
    {
        ++*count ;
    }

    ~shared_ptr( )
    {
        --*count ;
        if ( *count == 0 )
        {
            delete ptr ;
            delete count ;
        }
    }
} ;

これを見ればわかるように、shred_ptr<T>は、ポインターの参照数を管理するためにlong型のオブジェクトを動的確保している。つまり、T型のオブジェクトのためのメモリに加えて、long型のオブジェクトのためのメモリを別々に確保する必要がある。

しかし、もしT型のなかにカウンターがあればどうだろう。メモリ確保は一回で済む。メモリ管理によるオーバーヘッドが減少し、データの局所化によるキャッシュの恩恵も受けられる。


class UserData
{
    long count = 1 ;
    // その他のデータ

public :
    void increment() noexcept { ++count ; }
    void decrement() noexcept { --count ; }
    long use_count() noexcept { return count ; }
} ;

あとは、要素型の中のカウンターの取得、インクリメント、デクリメントをする方法さえ共通化してしまえばよい。この提案では、mixin設計を採用している。

要素型Tは、reference_count<T>を基本クラスに持つことでカウンター処理を提供できる。

class UserData : public std::reference_count<UserData>
{
// その他のデータ
} ;

不自由なMicrosoft Windowsが使っているクソみたいなAPIであるCOMのリファレンスカウントのような独自のリファレンスカウントのAPIに対応するには、retain_traitsを使う。

template < >
struct retain_traits<IUnknown>
{
    static void increment ( IUnknown * ptr ) noexcept
    { ptr->AddRef() ; }
    static void decrement ( IUnknown * ptr ) noexcept
    { ptr->Release() ; }
} ;

retain_ptrはリファレンスカウンターの値が0になっても、自動的にdeleteを呼び出してくれない。deleteを呼び出すのはretain_traits::decrementの役目である。Micorsoft WindowsのCOMの場合、deleteを呼び出す設計ではないため何もしていない。また、リファレンスカウントの値が取得できる場合は、long retain_tratis::use_count() noexceptを呼び出すと取得できる。COMの場合、リファレンスカウンターを変更せずに値を得る方法がなく、またその値もテスト目的のみであるとドキュメントに示されているので、実装しない。retain_traitsにuse_countメンバー関数がない場合は、retain_ptrのuse_countは-1を返す。

様々な場合に対処できる設計になっているのでなかなか悪くない。

ちなみに、retain_ptrはBoost.intrusive_ptrとは全く異なる設計になっている。これは、boostのintrusive_ptrは2001年当時の設計のままで、当時のC++の制約を受けているので、今の進化したC++の恩恵を受けることができないからだ。boostのintrusive_ptrと混同されることを考えて、名前もretain_ptrにしたそうだ。

reference_countの代わりにatomic_reference_countを用いることでリファレンスカウンターの増減をアトミック操作にできる。

また、掴んでいるポインターの解放はreleaseではなくdetachになっている。これはretain_ptrはポインターを所有しているわけではなく、ポインターの解放処理にもかかわらないため、その違いを区別するためにわざと別の名前にしているらしい。

悪くない。

P0469R0: P0469R0: Sample in place

inplace_sampleアルゴリズムの提案。

sample( first, last, out, n, g )は、[first,last)の範囲の値から乱数gを使ってmin( distance(first, last), n )個の標本をoutにコピーするアルゴリズムだ。

inplace_sample( begin, end, n, g )には、コピー先のoutがない。標本は[first,first+min( distance(first, last),n) )の範囲に配置される。イテレーターの参照する型のコピーのコストが重いか不可能で、swapのコストは軽い場合に便利なアルゴリズムだ。

これは追加されるべきだ。

ドワンゴ広告

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

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

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