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) ); }
ちょっと解説
transform_width
transform_width
以下のような感じ
#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) }
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つの状態がある
- 共有不可能
- 共有可能
- 共有中
共有不可能
非 const なイテレータを返す操作や、文字のリファレンスを返す操作を行ったとき(begin, end, insert, operator[]) に、共有不可能なオブジェクトになる。
ポインタを外部に漏らした瞬間、そのメモリは共有できないものになるということ。
共有不可能なオブジェクトは、常にコピーが行われる。
const な文字列は共有不可能なオブジェクトにはならない。
std::string a("hoge"); const std::string b("fuga"); a.begin(); // ここで、 a は共有不可能なオブジェクトになる b.begin(); // const なら大丈夫
また、 reallocate が発生するような処理が発生したとき、また共有可能なオブジェクトに戻る。
共有可能
代入によってコピーが行われない。まだ共有されていない文字列。初期状態の文字列。
共有中
文字列データが他のオブジェクトと共有され、参照カウンタによって管理されている。