IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

C++ の型変換

http://www.kuzbass.ru:8086/docs/isocpp/special.html#class.conv

クラスの型変換はコンストラクタと型変換関数を使って定義できる。これらの型変換は、ユーザー定義型変換と呼ばれ、暗黙的型変換、変数初期化子、明示的型変換に使われる。
ユーザー定義型変換は、型変換の定義が曖昧じゃないときにだけ適用される。型変換は、アクセス制御規則(private, public, protected)に従う。アクセス制御規則は、曖昧さの解決のあと適用される。
もっとも使われるユーザー定義型変換は、単一値への暗黙的型変換だ。

    class X {
        //  ...
    public:
        operator int();
    };

    class Y {
        //  ...
    public:
        operator X();
    };

    Y a;
    int b = a;                      //  NG: X().operator int() は呼ばれない
    int c = X(a);                   //  OK: X().operator int() が呼ばれる

ユーザー定義型変換は、曖昧でないときにだけ暗黙的型変換で使われる。子クラスの型変換関数で親クラスの他の型への型変換関数を隠すことはできない。関数オーバーロード解決(すごいややこしい)によって、最適な変換関数が選択される(その結果、曖昧と判断される可能性がある)。

    class X {
    public:
        //  ...
        operator int();
    };

    class Y : public X {
    public:
        //  ...
        operator char();
    };

    void f(Y& a)
    {
        if (a) {                    //  NG: Y には int と char へのユーザー定義型変換が定義されているので、曖昧
        }
    }

コンストラクタによる型変換

explicit なしの 1 個の引数でよびだせるコンストラクタの定義は、その引数の型から、そのクラス型への型変換を定義する。このようなコンストラクタを型変換コンストラクタと呼ぶ。

    class X {
        //  ...
    public:
        X(int);
        X(const char*, int =0);
    };

    void f(X arg)
    {
        X a = 1;                    //   a   =   X(1)
        X b = "Jessie";             //   b   =   X("Jessie",0)
        a = 2;                      //   a   =   X(2)
        f(3);                       //   f(X(3))
    }

explicit 有りのコンストラクタは、変数の直接初期化や明示的キャストしか出来ない。

    class Z {
    public:
    	explicit Z();
    	explicit Z(int);
    	//  ...
    };

    Z a;                            //  OK: 直接初期化
    Z a1 = 1;                       //  NG: 暗黙的型変換
    Z a3 = Z(1);                    //  OK: 直接初期化
    Z a2(1);                        //  OK: 直接初期化
    Z* p = new Z(1);                //  OK: 直接初期化
    Z a4 = (Z)1;                    //  OK: 明示的キャスト
    Z a5 = static_cast<Z>(1);       //  OK: 明示的キャスト

コピーコンストラクタは型変換コンストラクタだ。暗黙的に定義されるコピーコンストラクタは、 explicit なコンストラクタではないので、暗黙的型変換に使う事ができる。

型変換関数

あるクラス X の以下のような名前のメンバ関数は、

    conversion-function-id:
    	operator conversion-type-id

    conversion-type-id:
    	type-specifier-seq conversion-declaratoropt

    conversion-declarator:
    	ptr-operator conversion-declaratoropt

X から conversion-type-id への型変換を定義する。このようなメンバ関数を型変換関数と呼ぶ。引数と戻り値も定義しない。型変換関数の型は、引数無しで conversion-type-id を返すメンバ関数の型となる。型変換関数は、同じ型のオブジェクト同士の変換、親クラスからの変換、 void への変換には使われない

    class X {
        //  ...
    public:
        operator int();
    };

    void f(X a)
    {
        int i = int(a);
        i = (int)a;
        i = a;
    }

関数 f 内のすべての文で型変換関数 X::operator int() による型変換が行われる
ユーザー定義型変換は、変数初期化時や代入時でしか使われないということはない。(例えば、 if や、三項演算子の条件部や、 && のオペランド

    void g(X a, X b)
    {
        int i = (a) ? 1+a : 0;
        int j = (a&&b) ? a+b : i;
        if (a) {                    //  ...
        }
    }

The conversion-type-id shall not represent a function type nor an array type. The conversion-type-id in a conversion-function-id is the longest possible sequence of conversion-declarators.
This prevents ambiguities between the declarator operator * and its expression counterparts.

    &ac.operator int*i;             //  syntax error:
                                    //  parsed as:  &(ac.operator   int   *)   i
                                    //  not as:  &(ac.operator   int)*i

The * is the pointer declarator and not the multiplication operator.
Conversion functions are inherited.
Conversion functions can be virtual.

POSIX と Windows でのファイルオープン方法

各プラットフォームでのファイルオープンの仕方のメモ

たぶん、あってると思ってる

有ったら失敗、無かったら作る
// POSIX
open(name, O_RDWR | O_EXCL | O_CREAT, 0666);

// Win
CreateFileA(name, GENERIC_READ | GENERIC_WRITE, FILE_SHARED_READ | FILE_SHARED_WRITE | FILE_SHARED_DELETE, 0, CREATE_NEW, 0, 0);
有ったら開く、無かったら作る
// POSIX
open(name, O_RDWR | O_CREAT, 0666);

// Win
CreateFileA(name, GENERIC_READ | GENERIC_WRITE, FILE_SHARED_READ | FILE_SHARED_WRITE | FILE_SHARED_DELETE, 0, OPEN_ALWAYS, 0, 0);
有ったら開く、無かったら失敗
// POSIX
open(name, O_RDWR, 0666);

// Win
CreateFileA(name, GENERIC_READ | GENERIC_WRITE, FILE_SHARED_READ | FILE_SHARED_WRITE | FILE_SHARED_DELETE, 0, OPEN_EXISTING, 0, 0);

リズム的に「有ったでござる!無かったでござる!」って言いたくなった

iostream の状態について

書いとかないと忘れそうなのでメモ

gcc の basic_ios は以下のような operator void* と operator! を持っているので

      //@{
      /** 
       *  @brief  The quick-and-easy status check.
       *
       *  This allows you to write constructs such as
       *  "if (!a_stream) ..." and "while (a_stream) ..."
      */
      operator void*() const
      { return this->fail() ? 0 : const_cast<basic_ios*>(this); }

      bool
      operator!() const
      { return this->fail(); }
      //@}

以下のように

if (in.read(buf, size)) // operator void*
{
    std::cout << "OK";
}

や、以下のように

if (!in.read(buf, size)) // operator!
{
    std::cout << "NG";
}

のようなことができる。
内容は fail() が呼ばれているだけなので、以下のようなコードに等しい

in.read(buf, size);
if (!in.fail())
{
    std::cout << "OK";
}

read の前に eof か判断したい時は、以下のように peek を取ってみるといい

if (in.peek() != std::istream::traits_type::eof())
{
    std::cout << "EOF";
}

最初、以下のようにやってて全然ダメだった

if (in.eof()) // これは eof フラグが立ったかを見るだけで、peek() が eof かどうかは関係ない
{
    std::cout << "EOF";
}

Boost.Integer で signed な変数を unsigned に変換する

boost::uint_t を使うと出来るみたい

#include <boost/integer.hpp>

template <class T>
struct add_unsigned {
    typedef typename boost::uint_t<sizeof(T) * 8>::least type;
};

/************/

#include <iostream>
#include <typeinfo>

int main()
{
    std::cout << typeid(unsigned int).name() << std::endl;
    std::cout << typeid(add_unsigned<signed int>::type).name() << std::endl;
}

テンプレートを使って数値をリトルエンディアン形式のバイト列に変換する

boost/spirit/home/support/detail/integer/endian.hpp を参考に書いてみた

以下のような感じで定義しておいて

template <class T, std::size_t S>
struct little_endian {

    static inline void set(char* const buf, const T &t) {
        *buf = t & 0xff;
        little_endian<T, S - 1>::set(buf + 1, t >> 8); 
    }   

    static inline T get(const char* const buf) {
        return *buf | little_endian<T, S - 1>::get(buf + 1) << 8;
    }   
}; 

template <class T>
struct little_endian<T, 0> {

    static inline void set(char* const buf, const T &t) { } 

    static inline T get(const char* const buf) { return 0; }
}; 

以下のように使う

char buf[sizeof(int)];

// バイト列化
little_endian<int, sizeof(int)>::set(buf, 1000);

// 復元
int i = little_endian<int, sizeof(int)>::get(buf);

アセンブラを見てみる

たとえば、以下のようにインスタンス化してみると

#include <cstddef>

template <class T, std::size_t S>
struct little_endian {

    static inline void set(char* const buf, const T &t) {
        *buf = t & 0xff;
        little_endian<T, S - 1>::set(buf + 1, t >> 8);
    }

    static inline T get(const char* const buf) {
        return *buf | little_endian<T, S - 1>::get(buf + 1) << 8;
    }
};

template <class T>
struct little_endian<T, 0> {

    static inline void set(char* const buf, const T &t) { }

    static inline T get(const char* const buf) { return 0; }
};

// 明示的にインスタンス化する
template void little_endian<int, sizeof(int)>::set(char* const buf, const int &t);
template int little_endian<int, sizeof(int)>::get(const char* const buf);

以下のようなアセンブラになる

_ZN13little_endianIiLm4EE3setEPcRKi:
    movl    (%rsi), %eax
    movb    %al, (%rdi)
    movl    (%rsi), %eax
    sarl    $8, %eax
    movb    %al, 1(%rdi)
    sarl    $8, %eax
    movb    %al, 2(%rdi)
    sarl    $8, %eax
    movb    %al, 3(%rdi)
    ret

_ZN13little_endianIiLm4EE3getEPKc:
    movsbl  2(%rdi),%edx
    movsbl  3(%rdi),%eax
    sall    $8, %eax
    orl %edx, %eax
    movsbl  1(%rdi),%edx
    sall    $8, %eax
    orl %edx, %eax
    movsbl  (%rdi),%edx
    sall    $8, %eax
    orl %edx, %eax
    ret

テンプレートを使わない場合のアセンブラを見てみる

試しに、以下のように for 文を使うと

void little_endian_set(char* const buf, int a)
{
    for (std::size_t i = 0; i < sizeof(int); ++i)
    {
        buf[i] =a & 0xff;
        a >>= 8;
    }
}

int little_endian_get(const char* const buf)
{
    int r = 0;
    for (std::size_t i = 0; i < sizeof(int); ++i)
    {
        r = buf[i] | r << 8;
    }
    return r;
}

以下のようなアセンブラになった

_Z17little_endian_setPci:
    movb    %sil, (%rdi)
    movl    %esi, %eax
    sarl    $8, %eax
    movb    %al, 1(%rdi)
    movl    %esi, %eax
    sarl    $16, %eax
    movb    %al, 2(%rdi)
    sarl    $24, %esi
    movb    %sil, 3(%rdi)
    ret

_Z17little_endian_getPKc:
    movsbl  (%rdi),%eax
    sall    $8, %eax
    movsbl  1(%rdi),%edx
    orl %edx, %eax
    sall    $8, %eax
    movsbl  2(%rdi),%edx
    orl %edx, %eax
    sall    $8, %eax
    movsbl  3(%rdi),%edx
    orl %edx, %eax
    ret

あれ、あんまり大差ない?

boost::serialization の Dataflow Iterators で base64 生成

はじめに

base64 を作りたいと思って調べたら boost::serialization の Dataflow Iterators が便利そう。
Serialization - Dataflow Iterators

実際に書いてみた

#include <iostream>
#include <boost/pfto.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>

int main()
{
    using namespace std;
    using namespace boost::archive::iterators;

    typedef base64_from_binary<transform_width<istream_iterator<char>, 6, 8 > > base64_iterator;

    copy(
        base64_iterator(BOOST_MAKE_PFTO_WRAPPER(istream_iterator<char>(cin))),
        base64_iterator(BOOST_MAKE_PFTO_WRAPPER(istream_iterator<char>())),
        ostream_iterator<char>(cout)
    );  
}

このように、標準入力を base64 に変換して標準出力に出すくらいだったら、 1 ステートメントで書ける

ちょっと解説

transform_width

transform_width, 6, 8> は istream_iterator を 8 bit の数値列として、 6 bit ずつの数値をイテレーションする。
以下のような感じ

#include <iostream>
#include <boost/pfto.hpp>
#include <boost/archive/iterators/transform_width.hpp>

int main()
{
    using namespace std;
    using namespace boost::archive::iterators;

    unsigned char buf[] = { 0xff, 0xff, 0x00 };

    transform_width<unsigned char *, 6, 8 > it(BOOST_PFTO_WRAPPER(buf));

    // input 11111111 11111111 00000000
    // output 111111 111111 111100 000000

    cout << static_cast<int>(*it) << endl; // => 63 (00111111)
    ++it;
    cout << static_cast<int>(*it) << endl; // => 63 (00111111)
    ++it;
    cout << static_cast<int>(*it) << endl; // => 60 (00111100)
    ++it;
    cout << static_cast<int>(*it) << endl; // => 0  (00000000)
}
base64_iterator

6bit の数値から base64 の 1 文字をイテレーションする。

BOOST_PFTO_WRAPPER

これは、

template<class T> void f(T a)  { ... } //(どんな型でも OK)

template<class T> void f(T* a)  { ... } //(ポインタだけ )

のような関数テンプレートがあって f(&obj) とやったときにどっちのテンプレートを使っていいか分からないコンパイラのためにあるマクロ。
定義側を

template<class T> void f(BOOST_PFTO_WRAPPER(T) a)  { ... } //(PFTO_WRAPPER なやつだけ OK)

template<class T> void f(T* a)  { ... } //(ポインタだけ )

としておくと f(&obj) とやればポインタのほうだとわかるし、上のほうを呼びたければ f(BOOST_MAKE_PFTO_WRAPPER(obj)) と明示的にやってやればいい。

std::string まとめ

注意

gcc version 4.3.2 の std::string 私的まとめ

実装

  • クラス std::basic_string::_Rep は以下の情報を持つ
    • 文字列長 _M_length
    • 確保された容量 _M_capacity
    • 参照カウンタ _M_refcount
  • アロケータ(std::basic_string::_Alloc)の派生クラス(std::basic_string::_Alloc_hider)が文字列へのポインタをメンバとして持つ(_M_p)
    • アロケータは空のクラスであることが多く、そのための 1byte の容量を節約するため(empty base optimization)
  • std::basic_string のメンバは std::basic_string::_Alloc_hider のインスタンス _M_dataplus だけ
    • つまり、 sizeof(std::string) == sizeof(char*) 無駄がない。
  • std::basic_string::_Rep は _M_dataplus._M_p が指している領域の前に確保されている(_M_dataplus._M_p を std::basic_string::_Rep にキャストして 1 を引いた場所に確保されている)

参照カウンタの3つの状態

参照カウンタには3つの状態がある

  1. 共有不可能
  2. 共有可能
  3. 共有中
共有不可能

非 const なイテレータを返す操作や、文字のリファレンスを返す操作を行ったとき(begin, end, insert, operator[]) に、共有不可能なオブジェクトになる。
ポインタを外部に漏らした瞬間、そのメモリは共有できないものになるということ。
共有不可能なオブジェクトは、常にコピーが行われる。
const な文字列は共有不可能なオブジェクトにはならない。

std::string a("hoge");
const std::string b("fuga");

a.begin(); // ここで、 a は共有不可能なオブジェクトになる

b.begin(); // const なら大丈夫

また、 reallocate が発生するような処理が発生したとき、また共有可能なオブジェクトに戻る。

共有可能

代入によってコピーが行われない。まだ共有されていない文字列。初期状態の文字列。

共有中

文字列データが他のオブジェクトと共有され、参照カウンタによって管理されている。