個人的な観測範囲内での話ですが、「プログラマ」と呼ばれる人の中には、自分で構造体 (あるいはクラス) を定義することを面倒臭がり、これをしないで済ませようとする人が少なくありません。
彼らは、本来構造体として集約されるべきデータをプリミティブ (組み込み型) と標準ライブラリなどで与えられたコンテナ型を組み合わせることで表現しようとします。
例えば、地図上の登録地点の情報を CSV (各列にID, 名前, 緯度, 経度, 解説, ... が格納されている) から読み取るコードは以下のようになるわけです。
(csvParseLine は現在開いているCSVファイルから1行分のデータを vector<string> として取ってくる関数だと考えてください。)
class MyApplication {
//地点データ
vector<UINT> m_vID; //ID
vector<string> m_vName; //名前
vector<double> m_vLatitude; //緯度
vector<double> m_vLongitude; //経度
vector<string> m_vDescliption; //説明
//地点データの読み込み
int loadLocationData(){
m_vID .clear();
m_vName .clear();
m_vLatitude .clear();
m_vLongitude .clear();
m_vDescription.clear();
int nCount =0;
for (;;){
vector<string;> row =csvParseLine();
if (row.size < 5) break;
m_vID .push_back(static_cast<UINT>(atoi(row[0])));
m_vName .push_back(row[1]);
m_vLatitude .push_back(atof(row[2]));
m_vLongitude .push_back(atof(row[3]));
m_vDescription.push_back(row[4]);
++nCount;
} //for (;;)
return nCount;
};
} |
だからそうじゃねぇだろうが!!
成田
配列から重複した要素を取り除きたいとき、C++ では STL の std::unique を、Rubyでは Array#uniq を使うことが出来ます。
| C++ |
Ruby |
#include <iostream>
#include <algorithm>
int main(){
int v[] = {1, 2, 2, 3, 3, 3};
int len = sizeof(v) / sizeof(v[0]);
int *end = std::unique(v, v+len);
for(int *p = &v[0]; p != end; ++p)
std::cout << *p << ' ';
return 0;
} |
v = [1, 2, 2, 3, 3, 3]
p v.uniq |
|
1 2 3 |
1 2 3 |
これらの関数 (メソッド) は全く同じ働きをするように見えますが、よく調べてみると、実は微妙に異なる動作をすることが分かります。
異なる動作をするのは、具体的には次のようなケースです。
| C++ |
Ruby |
#include <iostream>
#include <algorithm>
int main(){
int v[] = {3, 2, 1, 2, 3, 3};
int len = sizeof(v) / sizeof(v[0]);
int *end = std::unique(v, v+len);
for(int *p = &v[0]; p != end; ++p)
std::cout << *p << ' ';
return 0;
} |
v = [3, 2, 1, 2, 3, 3]
p v.uniqpre> |
|
3 2 1 2 3 |
3 2 1 |
std::unique では望んだとおりの出力が得られません。
これは何故でしょうか。
田山
これまでの記事では、32ビット環境における64ビットの加法および減法について解説しました。
いよいよ、ヤマ場である乗法、すなわち「掛け算」のやり方について解説しましょう。
なお、今回も64ビット整数値を表現するために、以下の QWORD 構造体を使用します。
typedef unsigned long DWORD;
typedef struct {
DWORD dwLow; //下位32ビット
DWORD dwHigh; //上位32ビット
} QWORD; |
成田
Java では、配列の要素数をフィールド length を通じて参照することができます。
//合計の取得
int sum(int[] anData){
int nSum =0;
int i;
for (i=0; i<anData.length; ++i){
nSum +=anData[i];
} //i
return nSum;
} |
この length は (おそらく) final int、即ち、変更不能な public フィールドとして定義されており、これに対する代入操作を行おうとすると、コンパイルエラーとなります。
ところで、この仕組みは Java が「配列のサイズは変更できない」という仕様を有するが故に可能なものであるということに読者諸兄はお気づきでしょうか。
もし配列のサイズが初期化後に変更可能であるとすれば、length を final 修飾することはできません。
そうなると、length に対する代入操作を禁止する手立てはなくなってしまうのです。
一方、C++ ならば、値の変更が可能かつ、外部からの変更が不能なメンバ変数 (を模したインターフェイス) を定義することが可能です。
このエントリでは、このテクニックを使い、これまでのエントリで作成してきた Array クラステンプレート (参照) に要素数読み取り専用のメンバ変数 size を定義します。
成田
ビット演算を使用すると、1つの整数型データをビット単位で区切って、複数の用途に割り当てることができます。
例えば、符号なし整数 (unsigned int) のデータ長が32ビットの環境であれば、一つの変数で32個の真理値 (bool: true/false に相当) を格納することができます。
また、これらの真理値を複数個まとめて参照・操作することも可能です。
しかし最近のプログラミング環境では、潤沢なメモリやライブラリの普及などにより、ビット演算を利用するような場面は、以前と比べて少なくなっています。
そのため、ある程度長くプログラミングを学んでいるにも関わらず、ビット演算が使えない、あるいはその概念自体を知らないという人も見かけるようになりました。
というわけで、今回はビット演算の基本中の基本である、ビット値の参照・操作の方法とその手順について解説します。
ちなみに、当エントリの説明では図のスペース節約のため、8ビット符号なし整数 (BYTE: unsigned char) を操作の対象としていますので、実際に使用する場合は適当なデータ長に置き換えて考えてください。
成田
通常用いられる整数型 (32ビット符号付き) は -2,147,483,648 〜 2,147,483,647 の範囲の値を表現することができます。
殆どのアプリケーションにおいては、この制限が問題になることはまずありません。
しかし、この範囲を超える値を扱う必要のあるアプリケーションも存在します。
そこで考え付くのが、32ビット環境で64ビット整数を扱うための仕組みを作ることですが、実際にやってみると意外なほどに手間が掛ってしまいます。
この記事では、四則演算・10進出力などのプログラムを組むときの手順や考え方について解説していきます。
成田
さて問題です。次のコードの実行結果はどうなるでしょうか?
#include <iostream>
using namespace std;
int main(){
int a = 1, b = 2, c = 3;
int &n[3] = {a, b, c};
for(int i = 0; i < 3; ++i)
cout << n[i] << ' ' ;
cout << endl;
return 0;
} |
見出しを見た皆様のお察しの通り、正解は、「そもそもコンパイルされない」です。
code.cpp: In function `int main()':
code.cpp:7: error: declaration of `n' as array of references
code.cpp:10: error: `n' undeclared (first use this function)
code.cpp:10: error: (Each undeclared identifier is reported only once for each function it appears in.) |
このように、C++では参照の配列を作成することは出来ません。
しかし、一体何故でしょうか?
参照の配列によって簡潔に記述できるような処理に出くわすこともままありますし、参照の配列を作ることが原理的に不可能であるとは到底思えません。
それなのに、何故禁止されているのでしょう?
田山
32ビット環境で64ビット整数を扱う (加法編) の続き。
今回は、加法のコードに少し手を加えて、減法を行う関数を作成します。
念のため、前回のエントリで使用した64ビット整数を表現するための構造体 QWORD の定義を再掲します。
| qword.h |
#ifndef __QWORD_H__
#define __QWORD_H__
//32ビット整数
typedef unsigned long DWORD;
//64ビット整数
typedef struct {
DWORD dwLow; //下位32ビット
DWORD dwHigh; //上位32ビット
} QWORD;
#undef //__QWORD_H__
//[EOF] |
成田
C++ では、new を用いてオブジェクト (メモリ領域) を確保したならば、これを delete でを明示的に解放してやらなければなりません。
この「借りたもの (メモリ) は自分で返す」という硬派なスタイルは非常に C++ らしく、またパフォーマンスの面でも優れるため、個人的にはとても気に入っています。
その一方で、このスタイルにはメモリリークや二重解放によるアクセス違反, ダングリング・ポインタの発生といった問題があり、致命的なバグの原因となりがちであることも事実です。
また、プログラムの性質上、オブジェクトが利用されなくなるタイミングが予測しづらく、明示的な解放を記述することが原理的に困難であるような状況というものも存在します。
C/C++ 以外の殆どの言語はガーベジ・コレクションを言語仕様として採用することでこうした問題を回避しています。
特に、ガーベジ・コレクション手法の一つである参照カウントは、その動作原理が非常に単純であり、またそれ故に (一般的な状況下における) パフォーマンスにおいて他の手法よりも優れています。
そこで、この参照カウントの仕組みを C++ でも利用できないかと考え、Reference というクラステンプレートを作ってみました:
ソースコードは上記リンク先のページからダウンロードすることができます。
今回はこの Reference クラステンプレートの使い方について簡単に説明します。
成田
C言語では、キャスト (型変換) を以下のような形式で行います。
この記法では、どのような種類のキャストが行われるのかを明示したり、不正・危険なキャストに対してチェックを行うことができないという問題があります。
そこで、C++ では static_cast, dynamic_cast, const_cast, reinterpret_cast の 4つのキャスト演算子が導入されました。
今回は、これらのキャストの使い方について解説していこうと思います。
成田