スマートポインタの使い方 その2:shared_ptr
shared_ptrを使ってみる
unique_ptrが変数の寿命が尽きた段階でメモリ領域を開放するのに対し、shared_ptrは参照カウンタを持ち参照カウンタがゼロになるとメモリ領域を開放する。
#include <tchar.h> #ifdef __BORLANDC__ #include <boost/tr1/memory.hpp> namespace std { using namespace tr1; // 他のコンパイラと同様にstd::tr1をstdとする } #else #include <memory> #endif #include <iostream> // 円を表す図形 class CPrimitiveCircle { public: CPrimitiveCircle() : x(0), y(0), r(0) { } CPrimitiveCircle(int xx, int yy, int rr) : x(xx), y(yy), r(rr) { } int x, y, r; }; int _tmain(int argc, _TCHAR* argv[]) { std::shared_ptr<CPrimitiveCircle> c1(new CPrimitiveCircle(10, 20, 100)); // 振る舞いはあくまでもポインタ std::cout << "count = " << c1.use_count() << " r = " << c1->r << std::endl; std::shared_ptr<CPrimitiveCircle> c2 = c1; // 参照回数が増える std::cout << "count = " << c1.use_count() << " r = " << c1->r << std::endl; std::shared_ptr<CPrimitiveCircle> c3 = c1; // 参照回数が増える std::cout << "count = " << c3.use_count() << " r = " << c3->r << std::endl; return 0; }
あくまでも「ポインタ」として振る舞うので、メンバのアクセスはアロー演算子(operator->)で行う。use_countメソッドはshared_ptrが管理しているメモリ領域の参照数を取得する。
比較も同様。operator==とoperator!=で管理しているメモリ領域が「同一」か否かもチェックできる。
生ポインタの取得と再初期化
生ポインタの取得はunique_ptrと同様getメソッドでshared_ptrが管理している領域の生ポインタを取得出来る。当然、このメモリ領域はdeleteしてはならない。同様に、resetメソッドで別のポインタで再初期化できる。unique_ptrとの違いは参照回数が減るだけで、別の同一のメモリ領域を参照しているshared_ptrそのものには影響が無い。
int _tmain(int argc, _TCHAR* argv[]) { std::shared_ptr<CPrimitiveCircle> c1(new CPrimitiveCircle(10, 20, 100)); // 振る舞いはあくまでもポインタ std::cout << "count = " << c1.use_count() << " Addr = " << c1.get() << std::endl; std::shared_ptr<CPrimitiveCircle> c2 = c1; // 参照回数が増える std::cout << "count = " << c1.use_count() << " Addr = " << c1.get() << std::endl; std::shared_ptr<CPrimitiveCircle> c3 = c1; // 参照回数が増える std::cout << "count = " << c3.use_count() << " Addr = " << c3.get() << std::endl; // ポインタは同値 c1.reset(new CPrimitiveCircle(0, 0, 20)); // 別のポインタで初期化 CPrimitiveCircle* pCircle = c1.get(); std::cout << "count = " << c1.use_count() << " Addr = " << c1.get() << std::endl; std::cout << "count = " << c3.use_count() << " Addr = " << c3.get() << std::endl; // 再初期化した分参照回数が減る return 0; }
以下は実行結果。
count = 1 Addr = 505400 count = 2 Addr = 505400 count = 3 Addr = 505400 count = 1 Addr = 505470 count = 2 Addr = 505400 ← resetしたので、参照回数が減っている
operator boolでshaed_ptrが有効か否かのチェック、swapメソッドでポインタの交換が出来るのもunique_ptrと同様。
shared_ptrのちょっとした問題
shared_ptrは参照数で管理しているので、データ構造によっては直接的、間接的にshared_ptr同士が参照し合う現象(相互参照)が発生してメモリ領域が開放されない場合がある。以下の例は、多数の線分の接続関係からポリゴンを作成するロジックから抜粋したもの。
// 直線クラス class CPrimitiveSegment { public: CPrimitiveSegment() : sx(0), sy(0), ex(0), ey(0) { std::cout << "CPrimitiveSegment::CPrimitiveSegment()" << std::endl; } ~CPrimitiveSegment() { std::cout << "CPrimitiveSegment::~CPrimitiveSegment()" << std::endl; } std::shared_ptr<CPrimitiveSegment> next; // 接続先 int sx, sy, ex, ey; }; int _tmain(int argc, _TCHAR* argv[]) { // 線分を3本定義 std::shared_ptr<CPrimitiveSegment> s1(new CPrimitiveSegment()); std::shared_ptr<CPrimitiveSegment> s2(new CPrimitiveSegment()); std::shared_ptr<CPrimitiveSegment> s3(new CPrimitiveSegment()); // 三角形を作る s1->next = s2; s2->next = s3; s3->next = s1; // shared_ptrの参照数を出力 std::cout << "count s1 = " << s1.use_count() << std::endl; std::cout << "count s2 = " << s2.use_count() << std::endl; std::cout << "count s3 = " << s3.use_count() << std::endl; return 0; }
実行結果
CPrimitiveSegment::CPrimitiveSegment() CPrimitiveSegment::CPrimitiveSegment() CPrimitiveSegment::CPrimitiveSegment() count s1 = 2 count s2 = 2 count s3 = 2
参照回数が2のまま、main関数が終了しデストラクタが実行されない。いわゆる「メモリリーク」が発生する。
この場合、参照関係を一方通行にして、参照される側の参照カウンタを「変化させない」weak_ptrを使用する。
class CPrimitiveSegment { public: CPrimitiveSegment() : sx(0), sy(0), ex(0), ey(0) { std::cout << "CPrimitiveSegment::CPrimitiveSegment()" << std::endl; } ~CPrimitiveSegment() { std::cout << "CPrimitiveSegment::~CPrimitiveSegment()" << std::endl; } // shared_ptrから参照数を変化させないweak_ptrへ std::weak_ptr<CPrimitiveSegment> next; // 接続先 int sx, sy, ex, ey; }; int _tmain(int argc, _TCHAR* argv[]) { // 線分を3本定義 std::shared_ptr<CPrimitiveSegment> s1(new CPrimitiveSegment()); std::shared_ptr<CPrimitiveSegment> s2(new CPrimitiveSegment()); std::shared_ptr<CPrimitiveSegment> s3(new CPrimitiveSegment()); // 三角形を作る s1->next = s2; s2->next = s3; s3->next = s1; // shared_ptrの参照数を出力 std::cout << "count s1 = " << s1.use_count() << std::endl; std::cout << "count s2 = " << s2.use_count() << std::endl; std::cout << "count s3 = " << s3.use_count() << std::endl; return 0; }
以下は実行結果。参照回数は変化せずにデストラクタが正しく実行されている。
CPrimitiveSegment::CPrimitiveSegment() CPrimitiveSegment::CPrimitiveSegment() CPrimitiveSegment::CPrimitiveSegment() count s1 = 1 count s2 = 1 count s3 = 1 CPrimitiveSegment::~CPrimitiveSegment() CPrimitiveSegment::~CPrimitiveSegment() CPrimitiveSegment::~CPrimitiveSegment()
TBalloonHint::ShowHintの使い方
TBalloonHint::ShowHintに問題があるみたいで、代替にJVCLのTJvBalloonHintを使うというエントリーを書いたのだけれども、Embarcaderoの高橋さんからコメントがあって、再テスト。自分の勘違いで問題が無いことが確認できたのだけれども、以下の点に注意。
- TBalloonHintは動的に生成せずにフォームに貼ったコンポーネントを使い回すこと。
- ShowHintメソッドで指定する座標はフォームの座標ではなく、スクリーン座標を指定する。
- ShowHintメソッドはヒント表示後すぐに終了する。HideAfterプロパティで指定した時間を待たないので、直後にdeleteなどで破棄するとヒントそのものが表示されない。
以下は正しく動作するコード例。TBalloonHintはコンポーネントとしてフォームに貼ること。
C++Builderの例:
void __fastcall TForm1::Button1Click(TObject *Sender) { BalloonHint1->Title = _T("ヒントのタイトル"); BalloonHint1->Description = _T("バルーンヒントを表示してみる。"); BalloonHint1->HideAfter = 2000; BalloonHint1->ShowHint(Button1->ClientToScreen(Button1->ClientRect.CenterPoint())); }
Delphiの例:
procedure TForm1.Button1Click(Sender: TObject);
begin
BalloonHint1.Title := 'ヒントのタイトル';
BalloonHint1.Description := 'バルーンヒントを表示してみる。';
BalloonHint1.HideAfter := 1500;
BalloonHint1.ShowHint(Button1.ClientToScreen(CenterPoint(Button1.ClientRect)));
end;
でも、TBalloonHint::ShowHintのヘルプがないんだよな…。
スマートポインタの使い方 その1:unique_ptr
スマートポインタって何?
C++において、operator newでメモリ領域(ヒープ領域)を動的に確保した場合、その領域はoperator deleteでプログラマが責任を持って解放してやらなければならない。しかし、deleteを書き忘れたり、例外が発生したときの処理を怠った場合など、それが正しく行われないことはよくある。正しく解放されなかった領域はOSやプロセスが使用可能なメモリ領域を「不正占拠」し、それが積もり積もると、OSやプロセスが停止する場合がある。
#include <memory> class Mess {}; void f(Point p1, Point p2) { Rectangle* r(new Rectangle(p1, p2)); r->rotate(45); // 矩形を45度回転 // ... if (in_a_mess) throw Mess(); // 例外を投げてみる // ねぇ、例外が投げられたときの後始末は? delete r; }
そこで、プログラマの負担を軽減すべく、そのメモリ領域の「寿命」の管理を自動的に行う「スマートポインタ」がC++の拡張ライブラリであるBoostに実装された。その後、C++の新規格であるC++0x(C++11)でBoostの実装をベースにしたスマートポインタがC++標準ライブラリに取り込まれた。*1
スマートポインタとは文字通り"smart"(気の利いた)なポインタのことで、スマートポインタが管理しているメモリ領域が不要になったら自動的に解放してくれる。*2メモリ領域を解放するタイミングの違いによりいくつかのタイプがある。
- std::unique_ptr (boost::scoped_ptr) : 変数の寿命が尽きるとそのメモリ領域を開放する。*3
- std::shared_ptr (boost::shared_ptr) : 参照カウントを持ち、参照カウントがゼロになるとそのメモリ領域を開放する。
- boost::intrusive_ptr : プログラマが参照カウントを管理する。
- boost::scoped_array : boost::scoped_ptrの配列版
- boost::shared_array : boost::shared_ptrの配列版
もちろん、振る舞いはあくまでも「ポインタ」なので、見た目は通常のポインタ関連のコードと何ら変わりが無い。
#include <memory> class Mess {}; void f(Point p1, Point p2) { std::unique_ptr<Rectangle> r(new Rectangle(p1, p2)); // unique_ptrはブロックの外に達するとメモリ領域を自動開放する r->rotate(45); // 矩形を45度回転 // ... if (in_a_mess) throw Mess(); // 例外を投げてみる // 途中で例外が投げられてもブロックの外に出たら自動的にメモリ領域の開放を行う // operator deleteは不要。スマートポインタが解放する。 }
std::unique_ptrを使ってみる
std::unique_ptrはブロックの外に抜けるなど変数の寿命が尽きたら、管理しているメモリ領域を自動的に開放するスマートポインタ。その名の通り"unique"(唯一)のメモリ領域しか管理しないので、複数のunique_ptrのインスタンスが同一のメモリ領域を管理することはあり得ない。元々、C++の標準ライブラリにはスマートポインタとしてauto_ptrがあったが、仕様に少々問題があった為にC++0xからは使用が非推奨となった。その替わりとしてunique_ptrが導入された。
使い方はコンストラクタの引数にoperator newで生成したポインタを渡すだけ。*4以後、そのメモリ領域はunique_ptrの管理下に置かれる。引数を与えなかった場合は、unique_ptrが管理しているメモリ領域は「無し」とみなす。使用するシチュエーションとして考えられるのは、pimplイディオムの実装部分や、Abstract Factoryパターンが生成したオブジェクトを自動的に解放したい場合など。
#include <iostream> #include <memory> class TestObject { public: TestObject() { std::cout << "TestObject::Constructor" << std::endl; } ~TestObject() { std::cout << "TestObject::Destructor" << std::endl; } void message() { std::cout << "TestObject::message" << std::endl; } }; int main() { std::unique_ptr<TestObject> pObject(new TestObject()); // newしたオブジェクトをコンストラクタに渡す pObject->message(); // メンバ関数の呼び出し return 0; }
メンバへのアクセスはポインタと同様にアロー演算子(operator->)で行う。インスタンスへのアクセスはポインタ演算子(operator*)。当然、deleteも必要ない。ブロックの外に出ると変数の寿命が尽きるのでunique_ptrはデストラクタで自身が管理しているメモリ領域を開放する。ただし、operator=で代入することも出来ない。
void f(Point p1, Point p2) { std::unique_ptr<Rectangle> rb = new Rectangle(p1, p2); // 代入は駄目。初期化は必ずコピーコンストラクタで。 }
配列として連続した複数個のメモリ領域が欲しい場合は、[]をつけて宣言する。
#include <iostream> #include <memory> int main() { std::unique_ptr<int[]> a(new int[10]); for (int i = 0; i < 10; ++i) { a[i] = i; } for (int i = 0; i < 10; ++i) { std::cout << a[i] << std::endl; } return 0; }
生ポインタの取得
unique_ptrはあくまでもポインタをラップするユーティリティクラスである。コードの見た目をポインタのように見せているだけであり、型そのものが違う。Windows APIのような標準C++ではないライブラリやAPIを呼び出す場合、unique_ptrが管理しているメモリ領域の生ポインタが必要になる場合がある。unique_ptrが管理しているメモリ領域の生ポインタを取得するにはgetメソッドを使用する。注意しなければならないのはgetメソッドはメンバ演算子(operator.)で呼び出す。
// 地図の名前を取得するAPI int get_map_name(int MapID, char* buffer); void process_map_name(int MapID) { int length = get_map_name(MapID, NULL); // 必要な領域サイズを取得 std::unique_ptr<char[]> buffer(new char[length]); // 領域を確保 get_map_name(MapID, buffer); // エラー!!! get_map_name(MapID, buffer.get()); // getメソッドで生ポインタを取得する }
取得した生ポインタはあくまでもunique_ptrの管理下にあるので、自分でdeleteしてはならない。unique_ptrがメモリ領域を管理しているか否かは、getメソッドで生ポインタを取得するか、あるいは、operator boolでチェックする。
void f() { std::unique_ptr<TestObject> obj; // どこも管理していないunique_ptr if (!obj) { std::cout << "ptr is not enabled" << std::endl; } }
unique_ptrの再初期化
Abstract Factoryパターンのようにサブクラスで振る舞いが変化するオブジェクトを動的に生成する場合など、文脈によってunique_ptrを再初期化する必要が出てくる。unique_ptrはoperator=で代入(上書き)出来ないが、resetメソッドで別のポインタで再初期化できる。このとき、unique_ptrは自身が管理しているメモリ領域を解放するので、プログラマがdeleteでメモリ領域を明示的に開放する必要は無い。
#include <iostream> #include <memory> class TestObject { public: TestObject(int ID) : m_ID(ID) { std::cout << "TestObject::Constructor" << "ID = " << m_ID << std::endl; } ~TestObject() { std::cout << "TestObject::Destructor" << "ID = " << m_ID << std::endl; } void message() { std::cout << "TestObject::message" << std::endl; } int m_ID; }; int main() { std::unique_ptr<TestObject> pObject(new TestObject(100)); pObject->message(); // メンバ関数の呼び出し // 再初期化 pObject.reset(new TestObject(200)); return 0; }
上記コードの実行結果
TestObject::ConstructorID = 100 TestObject::message TestObject::ConstructorID = 200 TestObject::DestructorID = 100 TestObject::DestructorID = 200
resetの引数にNULLポインタを渡せば、明示的にunique_ptrが管理しているメモリ領域を解放することが出来る。メモリ領域を開放せずに、ただ単にunique_ptrとメモリ領域の関係を切り離す場合はreleaseメソッドを使用する。戻り値としてメモリ領域の生ポインタが返るので、その後は通常のポインタとして扱える。当然、解放が必要であれば明示的に解放しなければならない。
void f() { std::unique_ptr<TestObject> pObject(new TestObject()); TestObject* pp = pObject.release(); // unique_ptrとメモリ領域の関係を切り離す delete pp; // 明示的に解放しないとリソースリークする }
unique_ptrと代入演算子(operator=)
C++03まで標準であったauto_ptrが「問題」とされたのは、auto_ptr同士をoperator=で代入するとメモリ領域の所有者が=の右から左へ移り、代入元のauto_ptrの管理領域はNULLとなる為である。
#include <iostream> #include <memory> int main() { std::auto_ptr<int> a(new int(100)); std::auto_ptr<int> b(new int(200)); // 単なるポインタ同士の代入のつもりだが、実はポインタの所有権が移動する b = a; // aはどこも参照していない抜け殻なので、Access Violationが発生 std::cout << *a << std::endl; return 0; }
一見問題なさそうなコードだが、単純な代入文であるにもかかわらず副作用が発生するのはよろしくないので、unique_ptrはoperator=を隠蔽して明示的な代入を出来なくした。
#include <iostream> #include <memory> int main() { std::unique_ptr<int> a(new int(100)); std::unique_ptr<int> b; b = a; // operator=は隠蔽されているのでコンパイルエラーが発生する。 std::cout << *a << std::endl; return 0; }
unique_ptrをメンバに持つクラスにoperator=を定義する場合など、文脈によってはunique_ptr同士で「値」をやりとりしなければならない場合がある。unique_ptrは単一の領域しか管理しないので、単一性を担保するための戦略として「中身」のやりとり以外に、管理しているメモリ領域のポインタ値の交換、もしくは譲渡が考えられる。
#include <iostream> #include <memory> int main() { std::unique_ptr<int> a(new int(100)); std::unique_ptr<int> b(new int(200)); std::cout << "a = " << *a << std::endl; std::cout << "b = " << *b << std::endl; *a = *b; // unique_ptrの管理下にある領域の「中身」をやりとりする std::cout << "a = " << *a << std::endl; std::cout << "b = " << *b << std::endl; return 0; }
unique_ptrが管理している領域のポインタ値を「交換」する場合はswapメソッドを使う。
#include <iostream> #include <memory> int main() { std::unique_ptr<int> a(new int(100)); std::unique_ptr<int> b(new int(200)); std::cout << "a = " << *a << std::endl; std::cout << "b = " << *b << std::endl; a.swap(b); // unique_ptrが管理している領域を交換 std::cout << "a = " << *a << std::endl; std::cout << "b = " << *b << std::endl; return 0; }
ポインタを「譲渡」する場合は、C++0xで導入されたmove関数を使用する。
#include <iostream> #include <memory> int main() { std::unique_ptr<int> a(new int(100)); std::unique_ptr<int> b; if (a) std::cout << "a = " << *a << std::endl; if (b) std::cout << "b = " << *b << std::endl; // std::auto_ptrだと、b = a;に相当 b = std::move(a); // unique_ptrが管理しているポインタを譲渡 if (a) std::cout << "a = " << *a << std::endl; if (b) std::cout << "b = " << *b << std::endl; return 0; }
move関数はオブジェクトが保持しているリソースを戻り値に譲渡*5させるヘルパ関数で、結果、譲渡元には何も残らない。C++0xで導入された「ムーブセマンティクス」と「rvalue参照」を用いて実装されている。
CentOS 5.6で最低限のWebアプリ開発環境を構築する
半ば個人的な手順書。少々怪しいかも。すべてCUIで行う場合を想定。
PHP5.3をインストール
デフォルトのPHPは5.1なので、5.3をインストール。ただし、php53-pearが無いのでpearはPHP5.1のをインストール後アップグレード。最初はメッセージが色々出るけど気にしない。(参考:http://d.hatena.ne.jp/Akkiesoft/20110411/1302488866)
# yum remove php php-* # yum install php53 php53-* # yum install php-pear # pear upgrade --force Archive_Tar # pear upgrade --force Console_Getopt # pear upgrade PEAR
"pear list" でバージョンの確認が出来る。
Installed packages, channel pear.php.net: ========================================= Package Version State Archive_Tar 1.3.7 stable Console_Getopt 1.3.1 stable PEAR 1.9.2 stable Structures_Graph 1.0.4 stable XML_RPC 1.5.0 stable XML_Util 1.2.1 stable
Xdebugのインストール
# pecl install xdebug
ビルドが終わったら/etc/php.iniに以下の項目を追加する。
[zend] zend_extension="/usr/lib/php/modules/xdebug.so" xdebug.remote_enable=On xdebug.remote_host=192.168.X.X
xdebug.remote_hostはリモートデバッグをする場合の接続元のIPアドレス。
"system-config-securitylevel"ツールを使用してXdebugが使用するポート9000を開ける。
# system-config-securitylevel
Apacheの動作を確認
# chkconfig --list | grep httpd # chkconfig httpd on # /etc/init.d/httpd restart
PHPの動作を確認
関数phpinfoはプラグインの状態も表示してくれる。Xdebugが正しくインストールされればその情報も。
<?php phpinfo(); ?>
動作しない場合は、コマンドラインで実行してエラーメッセージを調べる。
# php /var/www/html/index.php
SELinux関連
SELinuxの管理ツールをインストール。SELinuxは有効にした方が無難かも。*1
# yum install setroubleshoot # chkconfig setroubleshoot on # /etc/init.d/setroubleshoot start
SELinuxの生ログは/var/log/audit/audit.log。「sealert -a /var/log/audit/audit.log」で詳細な内容をわかりやすく表示してくれる。
SELinux関連はこれらが参考になる。
一般ユーザーとsambaの設定
お約束の一般ユーザーの追加。SELinuxが有効なときにSambaのファイル共有を許可するにはsamba_enable_home_dirsの値を1にする。
# adduser a7m # passwd a7m # pdbedit -a a7m # setsebool -P samba_enable_home_dirs=1 # chkconfig --list | grep smb # chkconfig smb on # /etc/init.d/smb start
MySQL関連
初期設定
/etc/my.cnfを編集する。エンジンはInnoDBを使用し、デフォルトのエンコーディングをUTF-8にする。
[mysqld] default-character-set = utf8 ; ←追加 default-storage-engine=InnoDB ; ←追加 [mysql] default-character-set = utf8 ; ←追加
セキュリティ関連
"system-config-securitylevel"ツールを使用してMySQLが使用するポート3306を開ける。
# system-config-securitylevel # setsebool -P httpd_can_network_connect=1
MySQLのデーモンの起動
# chkconfig --list | grep mysqld # chkconfig mysqld on # /etc/init.d/mysqld start
パッケージのアップデート
最後にインストール済みのパッケージを更新する。
# yum check-update # yum update
*1:本当は完全に無効化したかったのだけど、ポリシーを理解すればそんなに敷居が高くない。
第19回 エンバカデロ・デベロッパーキャンプ セッションT3自己フォロー
参加者の皆様お疲れ様でした。プレゼンの最初でいきなりしくじってしまいましたが、何とか完走できたつもりです。不明な点等ありましたらコメントください。
C++Builderでの問題点
なぜか、IDEの言語が日本語だとC++Builderで作ったアドオンがデバッグ出来ない。*1どうも、IDEがデバッグシンボルを読み込んでくれない模様。非常に後ろ向きな解決方法として、すっぴんの仮想マシンを作成してそれにRAD Studio XEをインストール。インストールする言語は日本語と英語。BDSSetLang.exeでIDEの言語を英語にすると大丈夫っぽい。でも、自分の実機ではそれでもうまくいかないので他のアドオンとかの影響もあるのかも。*2 *3
デバッグ時にIDEをハングアップさせない方法
OTAを使うとIDEの本体にも影響がありハングするなどデバッグ時に不便なことがあり、それについて言及したのだけど、DEKOさんから「代替レジストリを使って見たらどうか」という指摘があったので試してみた。
デバッグ実行時のオプションの[実行|実行時引数]に"-rregkey"を設定すると、デバッグ時のRAD Studioが参照するレジストリがデフォルトのHKCU\Software\Embarcadero\BDSからHKCU\Software\Embarcadero\regkeyとなるので、オリジナルの環境に影響を与えないでアドオンのデバッグが可能。regkeyは任意の名前をつけられる。
この方法に気づいていればプレゼンに入れられたのに。orz
DEKOさんへ業務連絡
Twitterのアカウントをとってくれると嬉しいかな。ちょっとした連絡事項等が(ほぼ)リアルタイムかつ、パブリックに公開できます。
あと、「s/築/筑/g」でございます。まぁ、自分の名字を説明するのに便利だし、この手の違いはずっと慣れているので、UNICODEの包摂基準なんてどうということ無いです。(ぉ
サンプルコードについて(04/06)
セッションで使用したサンプルコードを http://a7m.sakura.ne.jp/SOURCE/DevCamp19_T3_Sample.zip にアップロードしました。ご自由に使ってください。
第19回 エンバカデロ・デベロッパーキャンプ開催形態変更
東北関東大震災の影響により、デベロッパーキャンプの開催形態がバーチャル(オンラインセミナー)形式に変更になりました。会場参加の場合、いつもは印刷したレジュメを会場で配るのですが、今回は事前にオンラインで配布することとなりますので注意してください。
で、おいらは、午後のセッションのトップバッターなのだけど、事前に配布したプレゼンにバグがあるかもしれない*1ので、その辺はご容赦ください。<(_ _)>
*1:まぁ、これは印刷したレジュメもそうなのだけど。
static_castとreinterpret_castの違いについて試してみた
昨日のネタのうち、キャスト関連は思い込みとか理解不足なところがあったので、実際に動かしてみた。
//--------------------------------------------------------------------------- #include <iostream> #pragma hdrstop #include <tchar.h> //--------------------------------------------------------------------------- #pragma argsused int _tmain(int argc, _TCHAR* argv[]) { double a = 123.4567; //int i0 = reinterpret_cast<int>(a); // error!!! [BCC32 エラー] File1.cpp(12): E2031 'double' から 'int' へのキャストはできない int& i1 = reinterpret_cast<int&>(a); int* i2 = reinterpret_cast<int*>(&a); int i3 = static_cast<int>(a); //int* i4 = static_cast<int*>(&a); // error!!! [BCC32 エラー] File1.cpp(12): E2031 'double *' から 'int *' へのキャストはできない int* i5 = (int*)&a; int& i6 = (int&)a; std::cout << sizeof(a) << std::endl; std::cout << sizeof(int) << std::endl; std::cout << i1 << std::endl; std::cout << *i2 << std::endl; std::cout << i3 << std::endl; return 0; } //---------------------------------------------------------------------------
感じとして、Cスタイルのキャストは何が何でも変換。結果についてはお構いなし。reinterpret_castはポインタ同士のように互いに関連性がありそうならばキャストする。static_castは暗黙の型変換が許されればキャストする。
上記コード例のint*とdouble*の場合、reinterpret_castは互いにポインタなのでOKだけど、static_castは型そのものが違うので、暗黙の型変換が存在しなくてエラー。「プログラミング言語 C++ 第3版」の170ページあたりと実際の実行結果(C++Builder XEとVC++2008で確認 )から解釈すると、こんな感じなのかな…。