トップページ | 2008年12月 »

2008年11月28日 (金)

VC++2008のキャスト演算子(その2)

(その2)はreinterpret_caststatic_cast。今までreinterpret_castを使ったことがなかったので、これは勉強メモです。


[1] テストプログラムは(その1)と同じ

class A {
public:
  virtual void a(){ cout<<"A\n"; }
};

class AA : public A {
  int  aai;
public:
  void aa(){ cout<<"AA"<<aai<<"\n"; }
  AA() { aai = 1; }
};

class AB : public A {
public:
  void ab(){ cout<<"AB\n"; }
};

A* a = new A();
A* aa = new AA();
A* ab = new AB();
static_cast<AA*>(aa)->aa(); // (1)
static_cast<AA*>(ab)->aa(); // (2)
reinterpret_cast<AA*>(aa)->aa(); // (3)
reinterpret_cast<AA*>(ab)->aa(); // (4)

[1-1] 「ランタイム型情報を有効にする」を「はい」としてコンパイル&実行してみると、

・ (1)(3)は期待通りの動きをする。
・ (2)(4)も実行できてしまう。どちらもaaiの値としてメチャクチャな値を使っている。
この結果だけ見るとstatic_castとreinterpret_castは同じ作用を持っているように見え、違いは何かわかりません。

[1-2] reinterpret_castによる無関係ポインタ間の無理矢理変換機能

同じプログラムに別のクラスZを導入する。

class Z {
  int  zi;
public:
  void z(){ cout<<"Z"<<zi<<"\n"; }
  Z(){ zi = 9; }
};

static_cast<Z*>(aa)->z(); // (5)
reinterpret_cast<Z*>(aa)->z(); // (6)

(5)がコンパイルエラー(C2440)になる。static_castではA*からZ*に変換できないのでreinterpret_castを使え、というメッセージがでる。つまり(6)のやり方。
(6)は確かにコンパイルができ実行もできるが、ziとしてメチャクチャな値を使っている。普通はこんなことはしないと思う。

[1-3] reinterpret_castはポインタ型と整数型との相互変換の機能もあるようだ。

unsigned long ad1 = static_cast<unsigned long>(aa); // (7)
unsigned long ad2 = reinterpret_cast<unsigned long>(aa); // (8)
reinterpret_cast<AA*>(ad2)->aa(); // (9)

・ (7)はコンパイルエラー(C2440)になる。
・ (8)はコンパイルができ実行もできる。(8)で整数化し、(9)で元に戻す。(6)よりは使い道があるかもしれない。(ポインタ型がunsigned longに収まるという前提。)

VC++2008のキャスト演算子(その1)

C++で分かりにくいのがキャスト演算子(static_cast/dynamic_cast/reinterpret_cast/const_cast)の使い分けである。
手元にあるC++の教科書「C++ 実践プログラミング」(Steve Oualline著 オライリー)は600ページぐらいあって単なる入門書ではないが、キャスト演算子の違いについて詳しく書いていない。

以下は復習を兼ねてのメモ書きです(その1)ではstatic_castとdynamic_castとの区別

「C++ 実践プログラミング」を資料1、「Boost  C++をチューンアップする最先端ライブラリ」(ビョルン・カールソン(著) 村上雅章(訳) ピアソン・エデュケーション)を資料2、として参照している。

使用したVC++は「Visual C++ 2008 Express Edition SP1」


[1] テストプログラム

class A {
public:
  virtual void a(){cout<<"A\n";}
};

class AA : public A {
public:
  void aa(){cout<<"AA\n";}
};

class AB : public A {
public:
  void ab(){cout<<"AB\n";}
};

A* a = new A();
A* aa = new AA();
A* ab = new AB();
static_cast<AA*>(aa)->aa(); // (1)
static_cast<AA*>(ab)->aa(); // (2)
dynamic_cast<AA*>(aa)->aa(); // (3)
dynamic_cast<AA*>(ab)->aa(); // (4)

[1-1] プロジェクトのプロパティ「ランタイム型情報を有効にする」を「いいえ(/GR-)」に設定したとき、(3)(4)でコンパイル時警告(warning C4541)は出るが、とりあえずコンパイルは正常終了する。
実行してみると、

・ (2)が実行できる。クラスAAのメンバー関数aa()が実行される。(これは結構すごい!)
・ (3)がランタイムエラーとなる。(プログラムを作った本人は、変数aaがAAのオブジェクトを保持していることに自信?があるのですけど)

[1-2] プロジェクトのプロパティ「ランタイム型情報を有効にする」を「はい(/GR)」に設定したとき、コンパイル時警告は無い。
実行してみると、

・ (1)(2)(3)(4)すべて実行できる。しかも、クラスAAのメンバー関数aa()が実行される。
・ (2)はともかく(4)が実行できてしまうのは何のためのランタイム型情報でdynamic_castなのだろうか。


[2] クラスAAだけ定義を変えた。

class AA : public A {
  int  aai;
public:
  void aa(){ cout<<"AA"<<aai<<"\n"; }
  AA() { aai = 1; }
};

[2-1] ランタイム型情報を有効にして実行してみると、

・ (1)(3)は期待通りの動きをする。
・ (2)はやはり実行できるが、aaiの値としてメチャクチャな値を使っている。static_castが危険であるといわれるのはこのあたりにあるのだろう。
(4)はランタイムエラーとなる。これが(自分の理解では)本来のdynamic_castの動きである
(2)と(4)の動きの違いがstatic_castとdynamic_castとの違いなのだろう。

[2-2] (4)のランタイムエラーについて。文献1は「不正な変換を試みると、例外がスローされ」(P434)としているが、文献2は「ポインタ型に対してdynamic_castが用いられ、変換に失敗した場合にはnullポインタが返される」(P52)としています。
C++の規格としてはどちらが正しいかは分かりませんが、(4)に関して言うと文献2の動きをしています。dynamic_cast自体は例外をスローせず、nullポインタを返しています。

2008年11月26日 (水)

VC++2008のRegex

VC++2008では「標準 C++ ライブラリ TR(technical report)1 拡張」において、Boostから取り入れられた正規表現ライブラリRegexが使える。以下は、これを使ってみて気がついた点のメモ書きです。

VC++2008は「Visual C++ 2008 Express Edition SP1」。 Boostは1.33.1で、VC++2003と組み合わせた。


[1] Boost版とTR版ではヘッダファイルと名前空間が異なるので、コンパイラのバージョンによってVC++2008以降はTR版を、それ以外はBoost版を使うようにした。

#if defined(_MSC_VER) && (_MSC_VER >= 1500)   // VC++2008以降
#include <regex>
#else
#include "boost/regex.hpp"
#endif

#if defined(_MSC_VER) && (_MSC_VER >= 1500)   // VC++2008以降
using namespace std::tr1;
#else
using namespace boost;
#endif

Boost版ではRegex用ライブラリファイルをリンクするようにプロジェクトのプロパティを設定する必要があるが、TR版ではその必要がない。


[2] 次のコード

regex reg1("(Colo)(u)(r)", regex::icase);
std::string s1 = "Colour, colours, color, colourize, COLOUR";
std::string s2 = regex_replace(s1, reg1, "$1$3");
std::cout << s2 << std::endl;

は、「Boost  C++をチューンアップする最先端ライブラリ」(ビョルン・カールソン(著) 村上雅章(訳) ピアソン・エデュケーション)のP137に載っている例で、イギリスの綴りcolourをアメリカの綴りcolorに変換するものだが、TR版では3行目でコンパイルエラーになる。

3行目を次のようにすると

std::string s2 = regex_replace(s1, reg1, std::string("$1$3"));

Boost版、TR版ともにコンパイルでき、実行結果は

Color, colors, color, colorize, COLOR

となる。

TR版ではフォーマット文字列として普通のC言語の文字列を受け付けるregex_replace関数が定義されていないようだ。


[3] regexのコンストラクタのflag_type引数に取り得る値はBoost版とTR版とで完全に一致していない。

たとえば、icaseは両方にあるが、perlはBoost版にあってTR版にない。

VC++2008のスマートポインタ

Visual C++の最近のバージョンでは「標準 C++ ライブラリ TR1 拡張」としてshared_ptrなどのスマートポインタが使える。

このスマートポインタはBoostから取り入れられたものらしいので、「Boost  C++をチューンアップする最先端ライブラリ」(ビョルン・カールソン(著) 村上雅章(訳) ピアソン・エデュケーション)のサンプルプログラムのいくつかをVC++付属のライブラリを使ってコンパイル&実行してみた。以下は気がついた点のメモ。

使用したVC++は「Visual C++ 2008 Express Edition SP1」


[1] Boostを使うソースとVC++付属のライブラリを使うソースの互換性を維持するため、ヘッダファイルのインクルード部分と、使用する名前空間の設定部分をコンパイラのバージョンによって切り替えるようにした。

#if defined(_MSC_VER) && (_MSC_VER >= 1500)   // VC++2008以降
#include <memory>
#else
#include "boost/shared_ptr.hpp"
#endif

#if defined(_MSC_VER) && (_MSC_VER >= 1500)   // VC++2008以降
using namespace std::tr1;
#else
using namespace boost;
#endif

実際のスマートポインタの使用法は同じになる。

shared_ptr<A> p(new A());


[2] boost::scoped_ptrは取り込まれていないが、なぜだろうか。shared_ptrがあれば十分と考えられたのだろうか。


[3] 上の文献のP20,P22,P25,P42にあるサンプルプログラムを実行してBoostによる場合と同じ動作をすることを確認した。(互換性テストにはならないでしょうが...)


[4] スマートポインタとは関係ないが、VC++2008ではassert.hを明示的にインクルードしないと、assert( )関数がコンパイルエラーになる。

2008年11月15日 (土)

文献「Boost C++をチューンアップする最先端ライブラリ」 【1】

「Boost C++をチューンアップする最先端ライブラリ」 (ビョルン・カールソン(著) 村上雅章(訳) ピアソン・エデュケーション)

 テスト環境:WindowsXP(SP2), VisualC++2003, Boost 1.33.1

サンプルプログラムをいくつかテストしてみて気がついた点を書いてみた。


[1] polymorphic_castのサンプル(53~54ページ)について、 VC++2003では「ランタイム型情報を有効にする(/GR)」を「はい」にしないと、コンパイルで「warning C4541... 」となる。


[2] lexical_castのサンプル(71~72ページ)について、

 (1)クラスlexical_castableのメンバ

virtual void read_(std::istream& i) const {
  i >> s_;
}

はコンパイルエラー C2679 となる。constを外した。

 (2)このままのソースだと実行時に数字を入力しても boost::bad_lexical_castがスローされてしまう。クラスlexical_castableにpublicメンバー

std::string operator()(){
  return s_;
}

を追加し、呼び出し側を

int i = boost::lexical_cast<int>(le());

とした。


[3] 第5章 Regexの130ページ。「Boostをビルドすれば、自動的にリンクが行われるようになるため(Windowsベースのコンパイラでも)、...」とあるが、VC++2003ではプロジェクトのプロパティで「追加のライブラリディレクトリ」に設定する必要があった。

自分の環境では「D:\Lib\boost_1_33_1\libs\regex\build\vc71」


[4] Anyのサンプル(152~154ページ)について、#include <string> を加えないとコンパイルエラー C2679 となる。

154~155ページのサンプルも同様。


[5] 第1章 Smart_ptrについて。shared_ptrなどに収容されたポインタを他のクラスにキャストしたいときどうすればよいのだろうか。 30ページにshared_ptrに対応するstatic_pointer_castの説明はあるが、サンプルプログラムが無いのは片手落ちの感。

ブログ開始

「ソフトウエア メモ」のページをブログ形式で始めました (2008/11/15)

トップページ | 2008年12月 »