2012年7月31日火曜日

単純な初期化の解説

前の節に続いてDirect2Dの解説をします。

Direct2Dの初期化

何を描画するかでどんなオブジェクトを初期化する必要があるかも変わってきますが、最低限必要なものとしてはファクトリとレンダーターゲットがあります。
以下はこの2つを初期化する例です。
-------------------
//ファクトリ
ID2D1Factory* pD2DFactory ;
//レンダーターゲット
ID2D1HwndRenderTarget* pHwndRenderTarget ;

//ファクトリの作成
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&pD2DFactory);

//レンダーターゲットのサイズの準備
RECT rect;
::GetClientRect(hWnd, &rect);
D2D1_SIZE_U size =
D2D1::Size<UINT>(rect.right, rect.bottom);

//レンダーターゲットの作成
pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hWnd, size),
&pHwndRenderTarget);
----------------------------
(完全なサンプルは前の節で示しています。)


ファクトリ( ID2D1Factory )というのはDirect2Dのリソース(描画用オブジェクト等)を作るための工場みたいなものです。Direct2Dを利用する時はまずはこれを作る必要があります。

ファクトリを作るにはD2D1CreateFactory()という関数を呼びます。
この時に引数で指定できるのはシングルスレッド用にするかマルチスレッド用にするかです。

ファクトリを使って次に説明するレンダーターゲットを作る事が出来ます。レンダーターゲットは重要なリソースです。


レンダーターゲット(ID2D1RenderTarget )は描画対象を管理するインターフェースです。この インターフェース を通して描画に必要な関数を呼び出せます。ブラシを作る時にも必要です。
この ID2D1RenderTarget にはいくつか派生の インターフェース があり、今回作ったのは普通のウインドウを描画対象にするための ID2D1HwndRenderTarge という インターフェース です。
(Direct3Dと同じ描画先に出力したい場合は別の インターフェース を使う必要があります。)

レンダーターゲットの作成は先ほど作成したファクトリのメソッド CreateHwndRenderTarget() を使って行います。
その際、いくつかの初期設定が行えるのですが、例で示したデフォルトの設定でハードウェアレンダリングによる高速な描画が期待できます。

描画の実行

をする時は、今作ったレンダーターゲットのメソッドBeginDraw()EndDraw()の間で行います。

例)
pHwndRenderTarget->BeginDraw();
//画面を一色で塗り潰す
pHwndRenderTarget->Clear(D2D1::ColorF(1.0F, 1.0F, 1.0F));

pHwndRenderTarget->EndDraw();

前節のサンプルでは描画テストをするRenderTest()という関数の中でブラシを作成していました。(リンク:ブラシの概要

// ブラシの作成 --------
ID2D1SolidColorBrush* pGreenBrush = NULL;

pHwndRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(0.0F, 1.0F, 0.0F), &pGreenBrush);
-----------------------

そしてBeginDraw()とEndDraw()の間で 四角形の描画等をしていました。
//四角形の描画-----------
D2D1_RECT_F rect1 =
D2D1::RectF(100.0F, 50.0F, 300.0F, 100.F);
pHwndRenderTarget->FillRectangle(&rect1, pGreenBrush);
----------------------------
そして開放していました。
pGreenBrush->Release();

リソースについて

リソースというのは型通りの説明をするならばCOMのIUnknownというインターフェースを継承するID2D1Resource型の派生クラスです。
簡単に言ってしまえばDirect2Dで使用する描画関連の情報です。

リソースにはビデオメモリに配置する物と、そうでない物があります。今回使用したリソースは
・ファクトリ( ID2D1Factory
・レンダーターゲット( ID2D1HwndRenderTarge
・ブラシ (ID2D1Brush)
の3つであり、
この内レンダーターゲットとブラシはビデオメモリと関連付けられます。
ビデオメモリと関連付けられるリソースはデバイス依存、そうでないリソースはデバイス非依存と呼ばれたりもします。ここで言うデバイスはグラフィック装置というニュアンスです。

リソースは単なる構造体のデータなどとは違い、Direct2DAPIによってメモリ管理がなされる、言わば「重めのデータ」あるいは「高貴なデータ」と言った感じがします。

今回のサンプルではデバイスの初期化に比重を置き、ブラシを使った図形の描画はRenderTest()内にまとめました。しかし短い時間に何度も描画する場合はリソースを頻繁に作成するのではなく、ファクトリやレンダーターゲットと同様にクラスメンバとして管理するのが良いでしょう。
ただし、今回利用したSolidColorBrush(単色のブラシ)に関しては毎回作ってもさほど重くもないとか。…また、アプリケーションによってはウインドウのリサイズ時にしか再描画の必要がなくてパフォーマンスが気にならないというケースもあるでしょうが。

ブラシの色の変更

今回作ったブラシの名前が pGreenBrushなので使う色の種類毎にブラシを用意しなければならない様に思えるかも知れませんが、そんな事はありません。 ブラシには色を変更するメソッドが用意されています。
以下はいずれもブラシを赤色に再設定する例です

pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f));
pGreenBrush->SetColor(D2D1::ColorF(0xFF0000));
pGreenBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red));

SetColor()の引数の中では
namespace D2D1の関数を使って色を作っています。

透明度の設定
は以下のメソッドで出来ます。
 pGreenBrush->SetOpacity(0.5);//半透明

SetColorに4つめの引数を与えて色の設定と一緒に設定する事もできます。
pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f, 0.5f));

リソースの解放

リソース型は IUnknownというインターフェースを継承すると上で書きました。リソースを解放するにはこのIUnKnownのRelease()というメソッドを使います。

//ファクトリとレンダーターゲットの解放例

pHwndRenderTarget->Release();
pD2DFactory->Release();

C++でnewしたオブジェクトを2回deleteしてはいけないのと同様、リソースも通常は2回以上 Release() してはいけません。

今回のサンプルのクラスをコピー(代入)禁止にしたのは、デストラクタでリソースを解放する様にしたからです(より正確に言うとデストラクタの中のD2D::DestroyDevice()というメソッドで開放処理をしています)。

2012年7月30日月曜日

デバッグウインドウへ出力する

Win32APIのソフト開発なら
#include <windows.h>とインクルードしていると思いますが、この時
OutputDebugString(L"この文字列が出力される");
 という関数でプログラムの実行中に下に表示される「出力」というウインドウに文字列を表示できます。(文字列の前につけているLはユニコード設定でのコンパイル用です。)


数字の出力
以下は、数字も出力できるようにテンプレート関数を定義して使う例です。(ブラウザによっては改行コードが文字化けしているかも知れません。また、意味もなく文字の色が変わっているかも知れません。)



#include <windows.h>
#include <sstream> 

//引数一つ
template < typename T > void ods( T tep)
{
 std::wstringstream ss;
 std::wstring st;

 ss << tep<< L"\n";
 st = ss.str();
 OutputDebugString(st.c_str());
};
//引数2つバージョン
template < typename T, typename T2 > void ods( T tep, T2 tep2)
{
 std::wstringstream ss;
 std::wstring st;

 ss << tep << tep2<< L"\n";
 st = ss.str();
 OutputDebugString(st.c_str());
};
//引数3つバージョン(ただし型の種類は2つで交互に配置)
template < typename T, typename T2 > void ods( T tep, T2 tep2, T tep3)
{
 std::wstringstream ss;
 std::wstring st;

 ss << tep << tep2<< tep3 << L"\n";
 st = ss.str();
 OutputDebugString(st.c_str());
};

int main()
{
int x = 3;


ods(L"x = ", x, L" センチメートル");

  getchar();
  return 0;
}
--------出力結果----------
x = 3 センチメートル
-------------------------
以上の例で定義したodsという名前の関数は内部でストリングストリームというクラスを使って数字を文字列に変換しているためsstreamというヘッダが必要に成ります。

ストリングストリームを継承してみた


#include <windows.h>
#include  <sstream>

// デバッグウインドウへ文字列を出力する関数OutputDebug()があることと、その関数がデストラクタから呼ばれる事以外は
//wostringstreamと変わらないクラス
class DebugStringStream : public std::basic_ostringstream <wchar_t, std::char_traits<wchar_t> >
{
public:
              //デストラクタによる自動出力
 ~DebugStringStream ()
 {
  OutputDebug();
 }
              //引数で改行量を指定して出力
 void  OutputDebug(int kaigyou = 1)
 {
   for(int i = 0; i < kaigyou; ++i)
   {
    *this << L"\n";
   }
    //大域空間のOutputDebugStringを呼ぶ
    ::OutputDebugString(this->str().c_str());
    //一回呼ぶと内容はクリアされる
    this->str(L"");
 }   
};

int main()
{
DebugStringStream dss;
dss << L"あ~テステス " << 180 <<L"度";
dss.OutputDebug(2);//これで出力


 double pai = 3.14159265358979;
 //doubleやfloatの精度を設定
 dss.precision(15); 
 dss << L"円周率 = " << pai <<L"…";


//デストラクタが呼ばれても出力される
  return 0;
}

//---出力結果----
あ~テステス 180度

円周率 = 3.14159265358979…
ーーーーーーーーー
こちらはIOストリームの整形機能が利用できるし、沢山の出力をしたい場合、速度面で有利です。
まぁ普通にストリングストリームを使うのとさほど違いはありません。

分割コンパイルの仕組みとか

他のファイルに書いた機能を使いたい時は

#include "ファイル名.拡張子"


という風に書いてファイルをインクルードします。
そうするとビルド時、前処理としてその場所にファイルが読み込まれ、それからコンパイルが行われます。

インクルードの仕組みは単純で、つまりこの時、単にソースコードが複製されているのだと言えます。

コンパイルはファイル毎に行われるので、インクルードされたファイルはその場所でコンパイルされますが、さらに元々のファイルでもコンパイルされるのが普通です。

変数や関数などを使う時、宣言は複数あっても良いが、定義は1度だけというルールがあります。

だからもしインクルードで読み込んだファイルと読み込む側に同じ定義があればコンパイルエラーになります。

さらにインクルードで読み込んだファイルに何らかの定義があれば、例え読み込む側に同じ定義がなくてもリンクエラーになります

インクルードで読み込んだファイルに定義があれば読み込む側と読み込まれる側の両方で定義が発生しますが、 コンパイルの段階ではファイル毎に別々に処理が行われるので問題がありません。
しかしリンクの段階で、インクルードによって複製されたコードが統合されるので、2重定義となってしまうのです。


だから通常は宣言だけを書いたヘッダファイルと定義を書いたファイルに分けて、ヘッダファイルを複数のファイルからインクルードするようにします。(VC++ではヘッダファイルの拡張子は.h、定義などの通常のソースコードの拡張子は.cppにします。本来、拡張子はファイルの内容に影響を与えませんが、開発環境がそれを区別するという事はあり得ます。)

  

コンパイラの役割

上でも書きましたが宣言は複数あっても良いが、定義は1度だけというルールがあります。宣言というのは変数で言うならメモリを確保しない名前だけの提示を言います。


例えばグローバルスコープに

int a;

と書けば、aは0で初期化されるのでメモリが確保され、宣言も定義も書いていることに成ります。

関数は

int function();

のように、関数の内容を書かなければ定義にはなりません。これを関数の プロトタイプ宣言と言います。

コンパイラは内容がなくてもプロトタイプ宣言だけ見れば
きっとどこかでまともな関数が定義されているはずだ」と信用してくれます。
だからまだ中身を書いていない内から、あるいはそのコンパイル単位に中身が登場しなくたって、

int a = function();

という風にその関数を使ってもコンパイルが通ります。

ここでコンパイラが責任を持つのは「functionという名前の関数がint型を戻すという事と、aという名前の変数がint型であるので、この式は型の使い方に問題がない」という部分です。

コンパイルはファイル毎に行われので、全体の何処かに本当に定義があるのかどうかに関しては無知なのです。統括の責任を負うのはリンカの役割です。


テンプレート関数のコンパイル


テンプレート関数はコンパイラから見える範囲に定義を書かないとリンクエラーになります。
テンプレート関数は、それを使用するコードを書かない限りは、実際の関数が生成されなかったりします。実際に使われた型のバージョンの関数だけを生成して無駄を省くためです。
cppファイルなどにテンプレート関数の実体を定義し、ヘッダファイルにプロトタイプ宣言してそれをインクルードしてその関数を使った場合、コンパイラはそういう関数があると信じることはできるので、コンパイルは通ります。ここまでは普通の関数と同じです。
しかし普通の関数がcppファイルをコンパイルする際に関数がコンパイルされるのに対し、テンプレート関数はそのファイル内でその関数を利用しない限りは実体化が起こりません。それでリンクの時点でその関数が見つからずにエラーになるらしいです。
その代わりヘッダファイルに定義を書いても大丈夫なのでむしろ便利かも知れません。

クラス宣言とポインタ


どこにも定義していないクラスの名前を次のように書いてもエラーにはなりません。

class MyClass; //MyClassというクラスを使うという宣言
MyClass* myclass; //MyClassのポインタの宣言

class MyClass;
という記述は、どこかにMyClassというクラスを定義しているのだという事をコンパイラに信じさせます。
MyClass* myclass;
は、MyClass型のポインタを使うという宣言であり、メモリを確保しないので実際のMyClassの定義を必要とはしません。

つまり上に書いたclass MyClass;があれば、MyClass型のポインタの宣言が出来るのです。

しかし
MyClass myclass;
と言う風にmyclass変数の定義を書くとMyClassのコンストラクタを呼ぶ必要があるのでMyClass型の実際の定義情報が必要になります。

クラス宣言を使うと、あるクラスの中のメンバとして他のクラス型を持たせる場合、ヘッダファイルに他のクラスのヘッダファイルをインクルードしなくても済みます。

例)
class MyClass;

class tess
{
 MyClass* myclass;
};

この場合、
#include "MyClass.h"は不要です。

2012年7月29日日曜日

Direct2Dの初期化と単純な描画コード

Direct2Dの解説は
プログラミングガイド から色々たどれば基本的な事は分かるのではないかという気がします。

このページではDirect2Dの基本的な利用方法 を参考にデバイスの初期化をやってみます。

まず、win32API的な普通のウインドウを出せる事が前提です。
(これはVC++のファイル→新規作成→プロジェクト→Win32プロジェクトとかで簡単に作れるので問題ないかと思います。)

インポートライブラリの追加

ソリューションエクスプローラーのプロジェクトを右クリックするかメニューのプロジェクトからプロパティを開き、構成プロパティ→リンカ→入力を開きます。

追加のファイルに「D2D1.lib」を指定します。



 Direct2Dの初期化部分は通常のウインドウの初期化部分と明確に分けて見通しを良くしたかったのでD2Dという名前のクラスを作ってそこに記述しました。そのクラスのCreateDevice()という関数で初期化の例を示しています。

はじめにこのクラスのヘッダファイル(D2D.h)と本体(D2D.cpp)を掲載します。
次にこのクラスの使い方を説明します。
最後にDirect2Dの解説をしようかと思いましたが、それは次節に譲ります。

D2D.h

/*  D2D.h
D2D:このクラスの説明

Direct2Dの初期化を行います。
ただしこのクラスではDirect3DやGDIとの相互運用はできません。
(ID2D1HwndRenderTargetの初期化を行うクラスです。)

ウインドウ作成した後、
 CreateDevice(const HWND hWnd)
 で作成済みのウインドウハンドルを渡して下さい。

 初期化の内容は
 ID2D1FactoryとID2D1HwndRenderTargetの作成です。
 --------------------------------------------
作成したリソースの開放処理はデストラクタで行われます。
 --------------------------------------------
 ウインドウのサイズ変更時にはResizeWindow(HWND hWnd)にウインドウハンドルを
 渡してください。
 (ウインドウプロシージャでWM_SIZEメッセージを受け取った時に呼ぶ例)
 
 case WM_SIZE: 
     d2d.ResizeWindow(hWnd);
  break;

これをやらない場合は、ウインドウサイズに合わせて描画対象も変形します。
-------------------------------------------
 RenderTest()で描画テストが行えます。
 (ウインドウプロシージャのWM_PAINTメッセージ受信時に呼ぶ例)

  case WM_PAINT:
  hdc = BeginPaint(hWnd, &ps);
  d2d.RenderTest(hWnd);
  EndPaint(hWnd, &ps);
  break;
------------------------------------------
*/
#pragma once
#include <D2d1.h>

class D2D
{
public:
 D2D();

 ~D2D(void)
 {
  DestroyDevice();
 };

 bool CreateDevice(const HWND hWnd);
 //D2Dの機能を使う前の初期化を行います。
 //引数には描画対象となる作成済みウインドウのハンドルを渡して下さい

void RenderTest();
 //描画テストです
 //この関数を呼ぶ場合はCreateDeviceで初期化を済ませる必要があります。

 void ResizeWindow(HWND hWnd);
 // ウインドウのサイズを更新します。
 // ウインドウプロシージャでウインドウサイズの変更の
 //通知を受け取った時等に呼んで下さい。
 
private:
 //コピーや代入は禁止しています。
 D2D(const D2D& t); 
 D2D& operator=(const D2D& t);

 void DestroyDevice();
 //CreateDeviceで初期化したD2Dのデバイスを解体します。
 //デストラクタから呼ばれます。

 ID2D1Factory* pD2DFactory ;//ファクトリ
 ID2D1HwndRenderTarget* pHwndRenderTarget ;//レンダーターゲット

};

D2D.cpp


#include "D2D.h"
#include <crtdbg.h>

D2D::D2D(void)
{
 pD2DFactory = NULL;
 pHwndRenderTarget = NULL;
}

bool D2D::CreateDevice(HWND hWnd)
{
 HRESULT hr; //成否メッセージ戻り値

    // Direct2D ファクトリの作成
 // レンダーターゲットを作るためにこれが必要です

 if(pD2DFactory == NULL)
 {
   hr = ::D2D1CreateFactory(     
   D2D1_FACTORY_TYPE_SINGLE_THREADED,&pD2DFactory_);     
   
   if(!SUCCEEDED(hr)) 
     return false; 
   
 }

 //< レンダーターゲット(描画対象)の作成 >
 // ウインドウとDirect2Dの描画装置を関連付けます。
 // ウインドウと関連付けた描画装置は
 //レンダーターゲットという名前で呼びます。
 //ブラシなどの描画道具の作成もレンダーターゲットが行います。
    if( pHwndRenderTarget == NULL) 
    { 
        RECT rect; 
        ::GetClientRect(hWnd, &rect); 
        D2D1_SIZE_U size = 
            D2D1::Size<UINT>(rect.right, rect.bottom); 

        hr = pD2DFactory->CreateHwndRenderTarget(
                D2D1::RenderTargetProperties(),  
                D2D1::HwndRenderTargetProperties(hWnd, size), 
                &pHwndRenderTarget);
        if(!SUCCEEDED(hr)) 
   return false; 
    } 

    return true; 
}

void D2D::DestroyDevice()
{
    if(pHwndRenderTarget != NULL) 
    { 
        pHwndRenderTarget->Release(); 
        pHwndRenderTarget = NULL; 
    } 
  if(pD2DFactory != NULL) 
    { 
        pD2DFactory->Release();  
        pD2DFactory = NULL; 
    }  
}

void D2D::RenderTest()
{
 //RenderTestは描画テストをする関数です。
 //この関数を呼ぶ場合はCreateDeviceで初期化を済ませる必要があります。
 //初期化がうまくいっていないとアサートします。
 _ASSERT(pHwndRenderTarget);

 //ウィンドウが他のウィンドウに覆われていたら描画処理をしない
    if(pHwndRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED) 
        return; 

 HRESULT hr; 
 // ブラシの作成 
 ID2D1SolidColorBrush* pGreenBrush  = NULL;

    hr = pHwndRenderTarget->CreateSolidColorBrush(        
            D2D1::ColorF(0.0F, 1.0F, 0.0F), &pGreenBrush); 
    if(!SUCCEEDED(hr))
  return;
 
 //---------描画開始--------
    pHwndRenderTarget->BeginDraw();  
 
    //背景クリア 
    pHwndRenderTarget->Clear(D2D1::ColorF(1.0F, 1.0F, 1.0F));      
 
    //円の描画 
    D2D1_ELLIPSE ellipse1 = 
        D2D1::Ellipse(D2D1::Point2F(120.0F, 120.0F), 100.0F, 100.0F); 
    pHwndRenderTarget->DrawEllipse(ellipse1, pGreenBrush, 10.0F); 
 
    //四角形の描画 
    D2D1_RECT_F rect1 =  
        D2D1::RectF(100.0F, 50.0F, 300.0F, 100.F); 
    pHwndRenderTarget->FillRectangle(&rect1, pGreenBrush);

    pHwndRenderTarget->EndDraw();
 //---------描画終了---------

 //ブラシの解放
 if(pGreenBrush != NULL) 
    { 
        pGreenBrush->Release(); 
        pGreenBrush = NULL; 
    }
}

void D2D::ResizeWindow(HWND hWnd)
{
    //レンダーターゲットの論理サイズの再設定 
 if(pHwndRenderTarget)
 {
  RECT rect; 
  ::GetClientRect(hWnd, &rect); 
  pHwndRenderTarget->Resize( D2D1::SizeU(rect.right, rect.bottom) ); 
 }
}

なお、VC++の設定及びwin32プロジェクトの作り方によってはD2D.cppファイルの頭に #include "StdAfx.h" を加える必要があります。

このクラスの利用方法


このクラス(D2D)のおおまかな利用方法は次の通りです。


1・D2Dクラスのオブジェクトを作成
2・ウインドウのハンドルを渡してDirect2Dを初期化
3・ ウインドウサイズの変更に対応
4・ 描画処理を記述

これらを、順を追って説明します。

1・D2Dのオブジェクトを作成


C++の基礎的な話になりますが、まずオブジェクトを作ります。
例えばオブジェクトの名前(変数名)をd2dにしたければ

 D2D d2d;

と言った感じです。

2・ウインドウのハンドルを渡して初期化

普通のwin32APIのプログラムならウインドウを作っている部分があると思います。例えば

hWnd = CreateWindow(・・・

 if (!hWnd)
{//初期化失敗時は終了
  return FALSE;
}

という様な部分です。
Direct2Dの初期化はこの後に D2DのCreateDevice(const HWND hWnd)を呼べばできます。その際、引数としてウインドウのハンドルを渡して下さい。

例えば次の通りです。

d2d.CreateDevice(hWnd)

CreateDevice(const HWND hWnd)は戻り値として初期化の成否を返すので、失敗した場合は次のようにしてアプリケーションを終わらせると良いかも知れません。

if (!d2d.CreateDevice(hWnd))
{
    //失敗したのアプリケーションを終わらせる。
}

3・ ウインドウサイズの変更に対応

ウインドウのサイズ変更時にはResizeWindow(HWND hWnd)にウインドウハンドルを 渡してください。
ウインドウプロシージャでWM_SIZEメッセージを受け取った時に呼ぶ例は
case WM_SIZE:
d2d.ResizeWindow(hWnd);
break;
といった感じです。
これをやらない場合は、ウインドウサイズに合わせて描画対象も変形します。

4・描画処理の記述

RenderTest()で描画テストが行えます。
 ウインドウプロシージャのWM_PAINTメッセージ受信時に呼ぶ例は

case WM_PAINT:
hdc = BeginPaint(hWnd, &amp;ps);
d2d.RenderTest();
EndPaint(hWnd, &amp;ps);
break;

という感じです。
実行結果はこんな感じです。


解体処理について

このクラスで使用したDirect2Dリソース等のオブジェクト類はデストラクタ任せで解体されます。つまり普通に作ったオブジェクトは何もしなくてもアプリケーション終了時に解体されます。

D2D* d2d;
d2d = new D2D;
という風にnewで作った場合は
delete d2d;
という風にdeleteした時点で解体されます。

Direct2Dの初期化

・・・さて、とりあえず上に書いたクラスの
CreateDevice(HWND hWnd)
というメソッドの中がDirect2Dを初期化する部分だと言えます。
簡単な解説は次の節で行うつもりです。

2012年7月23日月曜日

数値から文字列への変換(int,double,float→wchar_t文字列)

#include <string>
std::to_string(num);   
この関数に数値型を入れるとstd::string型が戻る。
今となってはこれだけの事なんだがC++11以前の環境では
下に昔書いた様なめんどくさい作業が必要だったのかも知れない。
============================================
#include <windows.h>
#include <iostream>
#include <sstream> //必須なのはこれ

int main()
{
 //変換したい数値を作成
 double pai = 3.1415926535897932;

 // 変換を担うストリングストリームの作成
 //basic_stringstreamテンプレートのwchara_t版がtypedefされたもの
 std::wstringstream ss; //それがwstringstream(char版はstringstream)

 //doubleやfloatの精度指定(任意)
 //整数と少数を合わせた有効な桁数でデフォルトは6
 ss.precision(17); 

 //始めに作った数値をストリングストリームに入れる
 ss << pai;
 
 //C++の標準出力もワイド文字様のwが付いたwcoutを使う
 std::wcout << ss.str();

 //文字を加えてみる
 ss << L" = 円周率";
 ss<< std::endl; //改行

 // 少数の精度を落とす
 ss.precision(7);
 // 小数の表示を指数形式にする
              ss.setf(std::ios_base::scientific, std::ios_base::floatfield);
             // 少数をストリングストリームに追加
             ss << 3.14;

 //デバッグウインドウに出力
 OutputDebugString(ss.str().c_str()); 
 
 //文字列をクリア
 ss.str(L""); 

              ss << std::endl;
 ss << 100;
 OutputDebugString(ss.str().c_str());
getchar();
}  
             


実行結果



解説

C++的な方法でintやfloat, double等の数字の型を文字列へ変換するには標準C++ライブラリの ストリングストリーム (basic_stringstream)というテンプレートクラスが使えます。
まず始めにこのクラスのオブジェクトに変換したい数字を入れます。
そして .str() というメンバでストリング(basic_string)というテンプレートクラスのオブジェクトに変換し、
さらにそのオブジェクトからC形式の文字列のポインタを得たいならストリングのc_str()というメンバを使います。

例としては・・上のサンプルで示した通りなのですが、簡単に説明するとwchar_tの文字列を作る場合なら、

std::wstringstream ss;
という風にストリングストリームを用意し、

ss << 3 <<L"これは3です";

という風によくあるC++のストリームのメソッドで文字や数字を入れて、
wchar_tのポインタを得る時は一旦wstringオブジェクトを作り、
例)
std::wstring wst = ss.str();

そこからポインタを引き出します。
例)
const wchar_t* pws = wst.c_str();

wstringオブジェクトが消えると、このポインタが指す文字列も一緒に消えるので注意しましょう。

なお、冒頭のサンプルに示したように
ss.str().c_str()
と書けば、見た目の上ではwstringの作成部分を省略できます。

最も簡単な例
std::wstringstream ss;
ss << L"円周率="<< 3.14 <<std::endl;
OutputDebugString(ss.str().c_str());

(OutputDebugString()はWindowsのIDEのデバッグウインドウに出力する関数です。)

関数にしておくと便利でしょうか?
template <typename T> std::wstring toWstr(T tep)
{
std::wstringstream ss;
ss << tep;
return ss.str();
};

使用例1
int number = 100;

OutputDebugString(toWstr(number).c_str());

使用例2
int number = 9999;
std::wstring wstr = L"この数字は…";
wstr += toWstr(number);
OutputDebugString(wstr.c_str());


なお、やたらOutputDebugString()するための関数を
定義しまくった例がこちらにあります…。

小数点型の精度

例中の
ss.precision(); は、
浮動小数点型用の精度設定のための関数であり、整数の部分と小数点以下の部分を合計した桁数で指定します。デフォルトは6です。


名前空間

 今回のプログラムは
using namespace std;
を打っておけば
std::
をはずして名前を使う事が出来ます。


charとwchar_tの切り替え


 初心者用の文字列処理の解説の多くが
std::string
とか
std::cout <<
 を使うのですが、このままでは近年のユニコード環境の関数へ渡すワイド文字(wchar_t型)の文字列は扱えません。


マルチバイト文字列(日本ではShift-JIS)とワイド文字列(Unicode, UTF-16)の違いの基礎的な話はこのサイトが参考になります。
ユニコードにも色々あるんですがWindowsプログラミングでのユニコードの文字列は16ビットのwchar_t型の配列で表し、これをワイド文字列と言います。

C++で定義されている文字列処理に関わるクラスの多くはそのままだと、char型の文字列です。しかしこれの頭にwを付けるとwchar_t型のクラスになります。
例えば
std::string はcharの配列を管理する文字列クラスですが、これのwchar_t版は
std::wstring;
です。

 細かい話をすると
これらのクラスはテンプレートで作られています。例えば

 stringは実際はbasic_stringという名前のテンプレートクラスであり、それををcharで作る時用に、typedefでstringと命名されています。wchar_tならwstringです。

↓こんな感じ
--------------
//stringの型
typedef basic_string<char, char_traits<char>, allocator<char> >
string;

//wsrtingの型
typedef basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >
wstring;
 
-------------

同様に basic_stringstream は
---------
typedef basic_stringstream<char, char_traits<char>,
allocator<char> > stringstream;
typedef basic_stringstream<wchar_t, char_traits<wchar_t>,
 allocator<wchar_t> > wstringstream;
--------
となっています。


ストリングストリームの詳しい解説はここ
 IOStreamライブラリ自体の解説はここ
が参考になるかも知れません。

2012年7月21日土曜日

_ASSERTで警告ダイアログを出す

元々C言語にassert()というマクロがありますがVC++では

#include <crtdbg.h>
をインクルードして
_ASSERT(0)
とか
_ASSERTE(0)
でプログラムを中断させる事が出来ます。

C言語のassertは<assert.h>というヘッダをインクルードすれば利用できますがVC++版の方が警告発生時のコード追跡が便利だったりするかも知れません。

アサートの仕組みは
カッコの中が0(つまり条件式がfalse)ならダイアログボックスで警告が出ます。それ以外ならそのまま通過して何も起きません。
 Debugビルドで開発中に、「まぁ、こういう条件にはならないだろうけど、もしなった場合はすぐに知りたい」とか、 「開発中に起こり得る仕様外の出来事なので、これで知らせよう」という状況で使うのが一般的かと思います。
Releaseビルドした場合はアサートの文自体がカッコの中も含めて丸々消えてしまうのでこのマクロを使いまくってもパフォーマンスの低下は起きません。

使用例

int*p = 0;

int GetNum()
{
 //pが初期化前(0のまま)だったらアサート
 _ASSERT(p);

//無事通過したので内容を返す
 return *p;
}

・・・つまり
クラス内のメンバが初期化前に使われそうになった時などに有効でしょう。
if文で分岐してエラーを返したい場合もあるかも知れませんが、想定外のパターンをすぐに発覚させていきたいという場合にはアサートしましょう。

あとは配列の範囲チェックとかに使う?
void Set(int id, int num)
{
int p[10];
_ASSERTE(id>=0 && id <10);
p[id] = num;
 }

_ASSERTと_ASSERTEの違いは警告時に()の中の条件式が表示されるかどうかです。条件式が表示されると警告が出た時に問題の箇所が分かり安いという利点があるのですが、いずれにせよVC++でビルド実行中なら「再試行」ボタンを押せば問題の箇所に飛べるのでさほど利点がないかも知れません。

_ASSERT(0 && "Σ (゚Д゚;)カクカクシカジカでテラヤバス!");
と書くと

このようになります。
&&の左側がfalseの時点でfalse確定なので右側の式は評価されないので普通にアサートされて、()の内容がそのまま表示されるという仕組みです。条件式だけを書くよりも原因の特定を容易にします。

しかし、自分でデバッグする場合はコード中の_ASSERTのそばになぜアサートを設けたかをコメントしておけば済むという噂もあります。

文字列をやたら使うとメモリを食うから、それが気になる場合はこちらではなく_ASSERTを使うようにとMSDNのCRT アサーションというページには書いてありました。

2012年7月20日金曜日

メモリリークを検出する

要点
#include <crtdbg.h>
とインクルードして、main()関数の頭とかに

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
を書いておくと、メモリリークがあった場合、アプリケーション終了時にデバッグウインドウにレポートが出力されます。


ーーー経緯ーーーーー
_CrtDumpMemoryLeaks()という関数を使うとデバッグ実行中にnewで確保したメモリをdeleteし忘れていないかチェックする事が出来ます。

例)
#ifdef _DEBUG
   #ifndef DBG_NEW
      #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
      #define new DBG_NEW
   #endif
#endif  // _DEBUG

int main()
{
   int *p = new int;
   int *p2 = new int[10];

 //現時点で開放していないメモリをデバッグ窓に出力
 _CrtDumpMemoryLeaks();
}

写真をクリックすると拡大します。

int *pは4バイト、
int *p2は40バイト消費している事が分かります。


必要なヘッダ
#include <stdio.h>
#include <crtdbg.h> //必須

解説

写真17行目の

_CrtDumpMemoryLeaks();
によって、プログラムの現時点でnewしたのに開放していないメモリをデバッグ窓に出力します。
これでどのアドレスの何バイトのメモリが未解放かが分かるのですが、アドレス渡されても意味不明だし、バイト数だけのヒントも心もとないのでmain()関数の前に書いたマクロによって、そのメモリを何処のファイルの何行目で確保したかという追加の情報が出力される様にしています。
このマクロは、newで確保する以前に書いておく必要があります。

一般的な使い方としてはアプリケーション終了前に
_CrtDumpMemoryLeaks();
を呼んでメモリリークをチェックします。

でも、出力ウインドウなんて毎回チェックしないかも知れないのでアサートと組み合わせてすぐに分かるようにした方が良いでしょう。

_ASSERT(!_CrtDumpMemoryLeaks());

(_CrtDumpMemoryLeaks()は、メモリリークが見つかった場合にTRUE(=1)を返します。)

ただし、アプリケーション終了時に_CrtDumpMemoryLeaks()を書くと言っても、newを使わないグローバル領域のオブジェクトのデストラクタがその時点で呼ばれていないのは当然なので、そのオブジェクトの内部でnewで確保した領域があるとそれがメモリリークとして検出されてしまうという問題が発生します。

・・・という分けで今まで書いたことは忘れた方が良いかも知れません。

いくつか試しましたが結局、アプリケーションの開始時に
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
を書いとくという方法が無難だと感じました。

プログラミングはあなた自身の冒険の旅です。
もっと良い方法はあなた自身が探しだして下さい・・・。
ドラクエを超えるのはドラクエだけ(なんのこっちゃ)

参考にしたMSDNの情報
_CrtDumpMemoryLeaks
CRTライブラリを使用したメモリリークの検出

型はメモリと演算を規定する

要点
変数の型は変数が確保するメモリのサイズを決めます。
また、変数に対してどんな演算をするのかも決めます。
------------


変数という言葉はいつでも使える言葉です。

int x;
と書いたら、int型のxという名前の変数の宣言になります。

これに対してオブジェクト(またはインスタンス)という言葉は、変数がメモリとしての実体を持った状態を指します。
つまり

int x;
という宣言の後に
x = 3;
と定義を書いて
(あるいは
int x = 3;
という風に宣言と定義を同時に書いて)

そしてさらにプログラムをビルド、実行してこのコードの部分を制御が通過した時に初めてxはオブジェクトになります。

この時、x は 3で初期化したのでメモリの何処か8ビットは

00000011 (10進数で3)
になっているはずです。

windowsならint型は32ビットなので・・・

00000000 00000000 00000000 00000011
確保したビットの量はこれくらいあるでしょう。

この様に、変数の型は変数がオブジェクトになった時にどれだけメモリを確保するかを決めます
 
そして型にはもうひとつ重要な性質があります。
それが

演算の規定

です。
ここで言う演算というのはメモリの操作の事です。

C++によるプログラミングは基本的に、型の定義によって
メモリの確保や演算の仕方を決めて行くことになります。

例えば
----------------
int x, y;

x = 1; //1で初期化(メモリの確保という演算)
y = 3;
x = y; //yを代入(代入という演算)

とか
printf("%d",x); //関数に渡されて内部の引数にコピーされる
とか
ns[3] = x; //配列に代入
とか
float f = x; //xをfloat型に代入
-----------------
等の演算があります。
つまり変数の扱い全てが演算だと言えます。
これら演算は、型の種類毎に違うのが普通です。演算が全て同じならば別の型を定義する必要がないからです。

例えば次の様にabという名前の変数を別々の型で定義したとします。

int a = 10;
float b = 10.0f;

この時、初期化が発生している訳ですが、その演算内容はintとfloatでは違います。floatは少数が扱えるデータ構造にフォーマットしないといけないからです。当然ながら四則演算もメモリの配置が違うので同じやり方を適用できません。また、intとfloat等の別の型同士の演算をする時は、一方をもう一方の型に変換してから計算が行われます。この型変換というのも一種の演算であり、型同士の種類によって規則が違います。

intやfloatの様にC++に元から組み込みまれている基本型は初めからどのような演算を適用するかが決められていますが、構造体やクラス型はプログラマが演算を定義できます。(と言って大抵はその内部で基本型とその演算を使う事になります。)

2012年7月17日火曜日

newによるメモリの確保

----要約-----

//自由領域にオブジェクトを作りたい時
int* i = new int; //単体
int* is = new int[10]; //配列
MyClass* mc = new MyClass(9999);//クラス

//自由領域のオブジェクトを解体したい時
delete i; //単体
delete [] is; //配列
delete mc; //クラス

 -----解説----

変数を作る時には普通のスコープに従う作り方と自由領域に確保する作り方の2種類があります。

int i = 3;
と書いた時は、通常の作り方です。多分、勉強して始めに覚える作り方です。
一方
 int* i = new int;
と書いた時は自由領域に確保する作り方です。
これはnewという演算子で確保したint型のメモリのアドレスをポインタ型の変数で受け止めています。
ではnewで作ったオブジェクトと通常のオブジェクトは何が違うのでしょうか?
-----------------------------
その前にポインタの基本を確認しておきましょう。

newを使って作ったオブジェクトはポインタで受け止めるので、そのオブジェクトを使いたい時はポインタを使う事になります。
ただし、配列の場合は、普通の配列のように使えます。


int* i = new int;
int *is = new int[10];
MyClass* mc = new MyClass;

*i = 3;//iの値は3になる

for(int i = 0; i <10; ++i)
   is[i] = i; //配列の各要素に0~9を入れる

mc->GetNantoka();

->はアロー演算子といいます。これを使うとポインタが指すクラスや構造体オブジェクトのpublicメンバにアクセスできます。
(*mc). GetNantoka();
と書いても同じ事です。
----------------------------------
さて、newの解説に戻ります。
通常のローカル変数はスタックという一時的なメモリの出し入れをする機構に確保されます。しかしnewで確保したオブジェクトはそこがグローバルスコープであろうとローカルスコープであろうとヒープというプログラムの持続中に永続的に使うための場所にメモリが作られます。


この写真の例は、newで確保したオブジェクトのアドレスだけは他と大分違うという事を示しています。

まぁここで大切なのは具体的なアドレスではなく
newしたオブジェクトはスコープを持たない
という事です。永続的に存在するのだからスコープは関係ないのです。この点はグローバル変数と似たイメージです。

int a = 0;
{
int b = 0;
int* c = new int;
 }
と書いた時、ここ(中括弧の外)ではaにはアクセスできてもbとcにはアクセスできません。アクセス出来ないどころかstaticを付けていない局所変数(ローカル変数)はスコープを抜けた時点で解体されるので変数bとcはデストラクタが呼ばれて破壊されています。

しかしそれにもかかわらずcのメモリ領域は残り続けます。
なぜでしょうか?
それはcというポインタ型の変数は確かにスコープを抜けた時点で破壊されますが、cが指していたnewで確保したメモリ領域はcとは無関係に存在し続けるからです。
いま破壊されたのはcというポインタ変数であって、ポインタ変数が指していたオブジェクトではないのです。

これがもしcがグローバル変数を指すポインタだったらどうでしょうか?

int a = 0;
{
int* c = a;
 }

この場合、newはしていませんがカッコを抜けた後にやはりcは破壊され、それにも関わらずcが指していたメモリaは生存し続けます。つまりここだけ見るとnewによる自由領域のメモリ確保はグローバル変数と似ています。

グローバル変数と違うのはオブジェクトの解体方法です。
 aは newしていないので、aが存在するスコープを抜けた時点で解体されます。例えばaがグローバル変数だとすれば、アプリケーションの終了時に解体されます。
 しかしにnewで作ったメモリ領域のオブジェクトの寿命はスコープとは無関係に残り続けます。

自由領域のメモリを解体するには、そのアドレスを指すポインタに対してdeleteをします。

例)
int* c = new int; //自由領域にintオブジェクトを作成
delete c; //自由領域のintオブジェクトを削除

ただし上の方で見た例の様に

{
int b = 0;
int* c = new int;
 }
ここ、つまりスコープを抜けた後にはポインタのcが存在しないのでdelete c とも書けない分けです。
・・・という分けでnewしたスコープやクラスがきちんとdeleteの面倒を見るように注意を払う必要があります。

----メモリリークについて----
確保したメモリをプログラム的にはもう使わないにも関わらずに解放する事を忘れていると、知らない間に(管理意識の及ばない部分、つまりバグとして)使えるメモリが減っていきます。これをメモリリークと言います。ただしアプリケーション終了時にOSが開放してくれる場合がほとんどだと思います。
いずれにせよメモリの管理が出来ていないのはメモリが減っていくという事実以前にプログラムの整理、管理が出来ていない事と繋がる気がします。プログラミングはメモリを加工したり管理する作業だと言えるので、前向きに捉えるとメモリリークはメモリの存在を意識するための良い教材かも知れません。
-----------------------------

ところで
int* c = new int;
int* d = c;
delete d;

と書いた場合はどうなるのでしょうか?
まず2行目を見ましょう。dはポインタであり、それに対してcのアドレスをコピーしたのでdとcは同じメモリ領域を指すようになりました。
こうなるともうどちらが正当なnewしたポインタだったのか区別が付きません。文法上の意味はどちらも同じであり、どちらか一方が偽物だという訳でもありません。
だからdelete dした時点でdが指していたメモリ領域は消えます。誰か一人がdeleteしてくれれば良いのです。そもそも物理的に同じものを指していたのですから。
つまり
newの数とdeleteの数が合っていれば、ポインタがいくつあろうと問題ありません

さらに、この場合はdがクラス等の他のスコープに所属するポインタ変数でも良いことになります。誰か一人、newした領域を指すポインタさんがどこかのスコープに生きていれば、そしてdeleteする事を忘れなければいつかはその領域を壊すことが出来るでしょう。(ただし一つのスコープで作ったポインタを別のスコープで消すと管理がややこしくなるかも知れません。)

これは勇者と魔王の関係と言えるかも知れません。
つまり
int *p = new int;
と書いた時、pが勇者であり、newが魔王です。
new「がっはっははははは!メモリは頂いた!この領域は永遠に俺のものだ!スコープによって寿命が尽きる普通の変数とは生命力が違うわ!!」
p「メモリを制御するためにお前を生んだだけだ。しかしお前の存在が見えるのは私か私の同胞だけ・・・!例え末代になろうともお前を消す事は忘れぬぞ!」

・・・ちょっとポインタの存在を強調し過ぎたかも知れませんが、まずはnewしたメモリ領域はdeleteされるまで存在し続けるというイメージが大切です。そしてそれに対するアクセス手段がポインタであると考えましょう。
・・・元来、ポインタ変数はどこでどの様に作ってどう消そうと無害です。ポインタ変数の中身はアドレスという数字に過ぎないからです。
しょせんは人間のレベルだと言えます。
それに対し、newしたメモリとはポインタがどうのこうのとは全く別の次元の場所に存在する何かなのです。

例えば、ポインタ変数とそれが指していたメモリ領域は別物なのでdelete した後でもポインタ変数は使い回せます。
例)
int *p = new int(100);
delete p;
p = new int;
delete p;
p = new int[99];
delete [] p;
 

連続でdeleteしてはならない

既にdeleteで解放したメモリに対してnewで新たなメモリを割り当てる事なく再びdeleteする事は認められていません。やると、システムが監視してくれていれば警告ダイアログが出てアプリケーションを終了させるでしょう。いくら魔王といえど2度も殺してはいけないのです。
2回毒殺しようとしたら・・・
・・・・毒が裏返った!
とか・・・まぁ例えはどうでも良いですが。

2度のdeleteというミスを防ぐ簡単な方法としてはdeleteしたらすぐにポインタに0を入れておく事です。アドレス0のポインタをヌルポインタと呼び、これにはdeleteが働きません。

 ヌルポインタは普通にポインタを使う局面においても、 アドレスを指していない初期化前のポインタだという事を表すために使えます。アドレス0番はプログラマーが使うアドレスとしては実在しない事が保証されている様です。

例えばコンストラクタ内やdelete時にポインタに0を入れておき、
if(p != 0)
 等でチェックすれば、アドレスが入っている状態かどうかを確認できます。
また、数値型として0を使う変数ではなく、ヌルポインタだという事を示すために
#define NULL 0
というマクロが良く使われます。

例)
// プログラム開始時
int *p = NULL;

// 使用時
if(p != NULL)
{
   p->Use();
}
else
{
//ここでnewするか、あるいは警告メッセージをだしたり
}

//削除
delete p;
p = NULL;

また、ifで分岐させないでアサートを使って仕様外の状況に対処するのもありでしょう。
例)
_ASSERTE(p);

_ASSERTE()マクロを使うには
#include <crtdbg.h>

というヘッダが必要です。
詳しくは_ASSERTで警告ダイアログを出すをどうぞ

2012年7月14日土曜日

ポインタ、アドレス、リファレンス(渡し)


この出力結果は、以下のコードを実行したものです。
尚、printfの改行の書式が文字化けしているかも知れません。
(\nはバックスラッシュ(\n)じゃなくて半角の円マークを使うのが元々のコードです。)


#include <stdio.h>

int main()

{
 int i = 3; // 普通の整数を表すint型
 printf( " i = %d \n", i ); //iの内容を表示
 printf( " &i = %d \n", &i); // iのアドレスを表示

 int* pi; // int型を指すことが出来るポインタ型の宣言

 //pi = i; // ポインタ型に内容を入れるのは間違い
 pi = &i; // &をiの前に付けてアドレスを入れる
  
 printf( " pi = %d  \n", pi ); // piの内容、つまりiのアドレス
 printf( "&pi = %d \n", &pi );// pi自体のアドレス
 printf( "*pi = %d  \n", *pi );// iの内容
 
 int &ri = i; // iのリファレンスを作る
 printf( " ri = %d  \n", ri ); //iの内容
 //printf( " *ri = %d  \n", *ri ); //*は不要
 printf( "&ri = %d  \n", &ri ); //ri、つまりiのアドレス


        getchar();

}

解説 

変数のアドレス
&変数名
という書式で得る事が出来ます。アドレスは変数がメモリ上の何処にあるかを表す番号です。

例)
int i = 3; // 普通の整数を表すint型
&i ; // 3ではなく、iのアドレスを表す

アドレスが具体的にどんな数になるかは、実行環境によってその都度変わるだろうし、普段意識する必要がない部分です。

ポインタはアドレスを格納する変数です。
普通、アドレスは整数による番号で表されるため、
何らかの整数が格納されているという意味では
int等の整数型と大差ありません。

しかしプログラマーもコンパイラも
それが単なる整数型じゃなくてアドレスを保存する
特別な整数型として認識しているため、
その型の利用の仕方に特別な手段が用意されています。
それが&、*、->等です。
また足し算の意味も
アドレスを直接足すのではなく型のサイズを単位とした移動量となります。

&を使えば変数のアドレスを得られるため、
その値を直接ポインタに格納できます。

冒頭に示した実行結果に
&i = 1572576
pi =  1572576
とあります。2つ同じ数字が並んでいます。
この結果は事前の代入
pi = &i
によってポインタpi = 変数iのアドレスとなったからです。

ポインタはそのままではアドレスですが、
*ポインタ
けば、アドレスが指す変数の内容を得ることが出来ます
つまり例中では
*piと書けば
iのアドレスではなくiそのものとなります。

ちなみに
ポインタ型の宣言、つまり例中の
int* pi;
においても*という記号を使うので間際らしいかも知れませが、型の宣言のための*と内容を得るための*は無関係です。強いて言えば言語開発者がポインタ関連だから同じ記号を使ったのでしょうか?覚えやすいかも知れませんが意味的には逆の作用ですね・・・。
int* pi;(ちなみにint *pi;という風にスペースの区切りを変えてもOK)の時は、int型をポインタ型にしますが、一旦アドレスを確保したポインタに対して再びアスタリスク(*)を付けると今度はint型に戻るのですね・・・。まぁ戻るといっても瞬間的な話であって*を付けなければポインタであり続けるのですが。


また、
*ポインタ は、それが指す変数の中身を知るだけではなく、操作する事ができます。

例)
int n = 10;
int* pn = &n;
*pn =50;

こうするとnの値も50となります。ポインタは変数のコピーではなくアドレスです。そして*を付けている間はそのアドレスが指す変数そのものになってしまという事です。

また、ポインタ型はポインタ型同士で代入が出来ます。
例えばさっきの例に続いて
int* pn2 = pn;
*pn = 99;
と書くと nの値は99になります。
つまりこの状況はnの値はnという変数自身とpnというポインタとpn2というポインタの合計、3つの変数からアクセスされ得る分けです。(ポインタも変数の一種です。)しかし依然としてintの値を確保したメモリ自体は一つしか存在していません。


その他、 ここでは説明しませんがポインタには newによるオブジェクトの確保という使い道 やインクリメントやデクリメントという、配列とセットで使う演算があります。


リファレンス(参照)は変数の別名です。
例えば
int i =3;
int& ri = i;
と書けばriをiとまったく同様に扱えます。ポインタと違って内容を得るの時に*riと書く必要はありません。また、ポインタではないのでアドレスを格納できないので
ri = &i;
とは書けません。ポインタと違ってリファレンスは、宣言すると同時にメモリ実体のある変数を入れる必要があり、アドレスやポインタ型変数を入れることは出来ません。だからポインタと違ってリファレンスが指す内容が実体のあるメモリか既に破壊された領域かを気にするケースも通常はないでしょう。

---やや細かい話-------
ポインタが指すアドレスが必ずしも他の変数のアドレスではなく、newによって独自に確保したオブジェクトである可能性があるのに対し、リファレンスは確実に他の変数のです。アクセス手段こそ普通の変数と同じですが、どこか別の場所にも同じ内容を指す変数があるとうい事には注意が必要です。
さらに言えば、ポインタの様にアドレスは代入できませんが、ポインタが指す内容は代入可能です。
例)
int *pi = new int(10);
int& ri = *pi;
ですから、使い方によってはリファレンスが指す内容が消失している場合もあり得ます。
----------------------

リファレンスもややこしい事に?あるいは覚えやすくするために?
 型名の後に付ける&はリファレンスの作成を意味しますが
既に出来ている変数の前に付ける&はアドレスを得るアドレス演算子として働くので&の意味を混同しないで下さい。
(例)
int i = 3;
int &ri = i;
//これで変数iがriという名前でも扱えるようになった
&ri; //←これは3という値が保存されているアドレス
&i; //←これも3という値が保存されている&riと同じアドレス

この時、
ri =5にするとiも5になるし、
i=5にしてもriも5になります。
別の複数の名前が一つの変数の実体をいじれるというところはポインタと同様です。

関数引数のリファレンス

ここで問題
void func(int& i)
今使われた&の意味はどっちでしょうか?
iのアドレスでしょうか?
それともiは何かのリファレンスなのでしょうか?
見出しでバレバレかも知れませんが答えはリファレンスです。


 一般的に関数の引数では、容量の大きい構造体やクラスオブジェクトの無駄な生成を避けるために、あるいは渡されたオブジェクトの値をいじる時にリファレンスを使います。
その際、呼び出し元の変数を変更したいのかしたくないのか用途をはっきりさせるために、変更しない場合は
void func(const int& i)
という風にconstを付けるのが一般的です。
constを付けて初期化された変数は値を参照はできますが変更はできないので、関数内部でiの値を変更するコードを書くとコンパイルエラーとなります。

ちなみにconstの利用は関数内部に限った話ではありません。
constは定数を作る時に利用できます。
例)
const float pai = 3.141592f;
pai = 0; //コンパイルエラー


関数引数の文法と仕組み

単純に言えば、関数の引数においてもそれが呼びされた時点で
(型名 変数名)
という風に()の中で型の宣言に応じて変数の生成が行われます
つまり
 void func(int i)
という風に&を付けなければint iをここで生成します。その際、呼び出し元の引数の内容を新しく生成した変数のメモリにコピーします。intではピンと来ないでしょうがクラス型の場合コピーコンストラクタが呼ばれる事になります。こうして値こそ呼び出し元の引数と同じだけれど、メモリアドレスは別な部分に全く新しい変数が生成される分けです(どうせ関数のスコープを抜ければ解体されるのに)。

難しく聞こえたかも知れませんがこれは何も特別なことではなく、
int a = 3;
int b = a;
とやった時に、bはaからのコピーで初期化した分けです。
同様に
func(a);
を呼んでやれば
void func(int i)  
{
//この時点で int i = a;
//が発生している
}
という話です。

一方、
void func(int& i)
という風に&を付けてリファレンス型にしておくと余計な変数は生成せずに呼び出し元の引数のリファレンスが渡されます。(あるいは、 呼び出し元の引数のリファレンスを生成したと言い換えてもいいかも知れません。)

先程の例で言うと
int a =3;
int b& = a; //bはaのリファレンス
なのと同じように

func(a);
を呼んだ時に

void func(int& i)  
{
//この時点で int& i = a; が発生してる。
//つまりaのリファレンスiが作られた
}
という事になります。


型のサイズとsizeof

sizeofという演算子を使えば変数や型のサイズがバイト単位で与えられます。

int i = 9999;
printf("%dバイト, sizeof i); //4バイト
printf("%dバイト", sizeof (int));//4バイト
//型名の場合は()で囲む必要あり。

iの中身は9999という値ですが、sizeofを付けた時に得られるのは変数の値ではなく確保したメモリのサイズです。従って上のどちらのprintfの出力もwindowsなら4バイトになるでしょう。

CやC++は言語仕様としては型のサイズを決めていません。処理系によってどの型がどれだけのメモリを確保するかは違います。今のところwindowsなら32bitOSでも64bitOSでもintは4バイトと決まっている様です。あとchar型が1バイトだという事ぐらいはおおよそどの処理系でも共通しているらしいです。

2012年7月13日金曜日

メモリの量と情報量

メモリの最小単位はビット(bit)です。
1ビットは0か1かの様に2通りの状態を表現できます。
2ビットあれば2つのビットを組み合わせて
00, 01, 10, 11の4種類の状態が表現できます。
つまり1ビット増えると情報量が2倍になります。
8ビット(1バイト)は256種類です。
32ビット(4バイト)は4294967296(約43億)の状態を表現できます。
表計算ソフトを使えばすぐにビットが持てる情報量を確認できます。
これはOpenOfficeを使った例です。



 
 良く整数型に32ビットが使われますが、普通のint型は正負の情報に1ビット使うので絶対値としてはその2分の1の値となり、さらに0から数えるのでそれも状態数の一つとしてカウントすると扱える最大数は-1になったりもします。
処理系によって型の容量は変わりますが扱える数の最大値と最小値は

#include < limits.h>
とヘッダを追加した上でINT_MAX等のマクロで得ることが出来ます。

intの最小値と最大値
printf( " %d ", INT_MIN );//-2147483648
printf( " %d ", INT_MAX );//2147483647


ビットを実感してみよう

 int a =32;
 a = a >> 1;  //16になる。

>>は、右シフトという演算子です。
これにより、変数aのビット列を右に1つずらしています。
それでどうして値が半分になるかと言うと
2進数で10進数の32を表すと
----------------------
32  16   8   4   2   1 ←ビットを1にした時に増える数
1   0    0    0   0   0  ←この行が実際のビット列(2進数)
----------------------
となるので
ビット列が右にずれると・・
----------------------
32  16   8   4   2   1 
  0   1    0   0   0  0  
----------------------
10進数で言うと16の値になる分けです。

2012年7月10日火曜日

デバッグ


デバッグモードとリリースモード

ビルドにはデバッグモードによるビルドとリリースモードによるビルドの2種類があります。画面上のコンボボックスから、あるいはソリューションのプロパティーなどから切り替えが出来ます。主な違いは次の通りです。
デバッグモード (Debug)
VC++、あるいはAPIやライブラリが備えるデバッグ用の機能をフルに発揮できます。例えば実行時に予期せぬ動作をチェックしてエラー情報を教えてくれます。基本的に開発中はこれを使いましょう。

リリースモード(Release)
デバッグの能力は低くなりますが高速に動作するコードを生成します。アプリケーション完成時など、だいたいバグもなく運行できると確認できてから使えば良いです。ただしコードによってデバッグモードとの速度差は結構生じるので、速度を気にするプログラムを作る時などは必要応じてこちらのモードもチェックします。

デバッガのブレークポイント機能

デバックの時にとても役立つのがブレークポイントの設置です。VC++ではエディタ画面の左端の部分をクリックするかF9キーを押すことでブレークポイントのオン/オフが出来ます。

ブレークポイントは赤い印で示されます。ブレークポイントを設定するとプログラムをデバッグモードで実行している時にその場所で一旦プログラムが停止します。
そしてその時、自分が使っている変数にどのような値が入っているかを確認する事ができます。

確認が終わったら再度プログラムを走らせる場合はF5,もうここでプログラムを終了させたい場合はShift+F5を押します。

その他

デバッグ全般の話題はこのサイトVC++の使い方) とか
MSDNのデバッグルーチン (玄人向け)とか。

ソリューションにプロジェクトを束ねる

例えばサンプルブラウザから展開したプロジェクト達をいちいちバラバラに立ち上げるのは面倒なので一つのソリューションにまとめた方が良いかも知れません。私はチュートリアル1のソリューションに他のチュートリアルのプロジェクトを追加しました。(ただしチュートリアル2と3は同じものなのでどちらか片方を追加すれば良いかも知れません。)やり方は次のような感じです。(ちなみに私が試してみて結果として大丈夫だったというだけで、正式なやり方がどうだとか、今後問題がないかとかは知れませぬが・・・)

1 プロジェクトの追加
ソリューションエクスプローラから「ソリューション」を右クリックして「追加」→「既存のプロジェクト」を選び、追加したいプロジェクトを指定します。


2 ビルドの設定
デフォルトの設定では初めのプロジェクト以外ビルドできないと思うのでソリューションエクスプローラから「ソリューション」を右クリックして「プロパティ」を開きます。


「共通プロパティ」の「スタートアッププロジェクト」の「現在の選択」にチェックを入れます。


これでソリューションエクスプローラから選択したプロジェクトをビルド・実行できるはずです。その際「デバッグ情報が見つからない、または一致しない」という様な警告がでた場合は一端そのプロジェクトをリビルドするかクリーンして下さい。

その他VC++の機能やショートカット等

 VC++には色々と便利な機能があるので
「ツール」メニューから「設定」で→「上級者用の設定」に変えた上で
色々と試してみるのが良いと思います。

特にメニューの「表示」から出せるクラスビューやオブジェクトブラウザーやブックマーク等は便利です。

あと、任意の単語を右クリックした時に出るメニューなんかも地味に便利なんじゃないでしょうか?「定義へ移動(F12)」したり、「すべての参照の検索」が出来たりします。
 すべての参照の検索の後、その結果がリストアップされます。そこをダブルクリックするとその部分にジャンプできます。
また、F8キーを押すと、リストアップされた参照箇所に次々と移動できます。
F8キーは、デバッグ時のエラーメッセージの該当箇所を追う時にも使えて便利です。


その他

・F1キーで開くHelpが、選択中の項目を説明してくれることがありますビルドエラー時、出力ウインドウでF1キーを押せば、該当するエラー内容を解説するページが開きます。他にも、プロパティの設定項目を選択中にF1等、意外とF1が役に立ちます。


・カーソルを合わせた行で、何も選択しないでCtrl+Cを押すと一行が丸々コピー対象になります。

・既に定義されている関数は途中まで名前を入力したらCtrl+スペースキーで残りを補完できる事があります。(インテリセンス機能)

・Ctrl押しながら-で前に編集してい箇所に戻れます。(前に戻るボタンと同じです。)
再び、元の方へ戻るにはCtrlとShiftを押しながら-です(Ctrlと+を押したくなるのだが・・・)。

他にも、VC++限定の機能というわけでもありませんが、
・Ctrl+マウスホイールで文字の拡大・縮小
・Ctrl+F で検索や置換
・Ctrl+Aで全選択
できます。

あと
VC++の便利な機能 (外部へのリンク)というページには、ショートカット系のネタが豊富です。


2012年7月7日土曜日

デバイス、イミディエイトコンテキスト、スワップチェーン


訳者メモ


これら3つのオブジェクトの型はチュートリアルコード中で次の様に宣言されています。
ID3D11Device* g_pd3dDevice = NULL;
ID3D11DeviceContext* g_pImmediateContext = NULL;
IDXGISwapChain* g_pSwapChain = NULL;

イミディエイトコンテキスト( g_pImmediateContext )はデバイスコンテキスト型(ID3D11DeviceContext)のオブジェクトです。型名とインスタンス名で名前が違うのはDirect3D11のデバイスコンテキストにはマルチスレッド用途に初期化する方法(ディファードコンテキスト)とシングルスレッド用途に初期化する方法(イミディエイトコンテキスト)があり、その違いを明確にする意図がある様です(参考(MSDN))。あと古来からあるWindowsプログラミングGDIにもデバイスコンテキストがあるので間際らしいような、あるいは概念的な類似があるのでむしろ分かり易いかも?

Tutorial 00: Win32 Basics

(訳注: Direct3D10 Tutorial 00: Win32 Basics はHelp内リンクなのでここには貼れませんがWindowsプログラミングでは同じみのウインドウ作成とメッセージループのコードを簡潔に解説した内容です。当サイトではWin32APIプログラミングの部分で解説しています。

Direct3D デバイス


(訳者メモ----------
デバイスを直訳すると装置です。漠然とした言葉ですが、Direct3Dで装置と言えば、(3D)CGを描画するための装置を指します。Direct3Dは内部でビデオカード等のグラフィック装置を操ります。我々はビデオカードに直接アクセスするようなコードを作成する必要はなく、Direct3DAPIによって抽象化された仕組みを通して描画機能を得る事になります。ここでいうデバイスとは、この抽象化された装置を差します。

なお、
C++のソースコード上から
ID3D11Device* g_pd3dDevice
の様に具体的なオブジェクトとしてDevice(デバイス)を見る事ができたりします。つまりDirect3Dが一つのクラス(COMインターフェース)としてデバイスを表現している訳ですが、ここではその他の基本的なオブジェクトも含めてデバイスと呼んでいる感じがします。
----------)