個人的な観測範囲内での話ですが、「プログラマ」と呼ばれる人の中には、自分で構造体 (あるいはクラス) を定義することを面倒臭がり、これをしないで済ませようとする人が少なくありません。
彼らは、本来構造体として集約されるべきデータをプリミティブ (組み込み型) と標準ライブラリなどで与えられたコンテナ型を組み合わせることで表現しようとします。
例えば、地図上の登録地点の情報を 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つのキャスト演算子が導入されました。
今回は、これらのキャストの使い方について解説していこうと思います。
成田
必要に迫られて、ローカルスコープを作り出す関数を作ってみました。
齋藤
Eyes, JAPAN では、ソフトウェア開発には専ら C++, Java, Ruby などの オブジェクト指向プログラミング言語を用いています。(一部例外もありますが。)
ところで、「"オブジェクト指向" とは何か?」と問われたとき、「○○○○のことだ」とはっきり答えることのできる方はいないのではないでしょうか?
それもそのはず、「オブジェクト指向 (プログラミング)」というのは、
といった概念・機能 (の一部または全部) を取り込んだプログラミング手法の総称であり、厳密な定義は存在しないのです。
とはいえ、これらの概念・機能はプログラムの構造を整理し、保守性 (可読性, 再利用性) を向上させるには非常に有用です。
個人的には、この「オブジェクト指向」というのは、プログラム (特に大規模なもの) を作成するための「本質的に正しい」方針だと考えています。
今回は、その中でももっとも分かりやすい (目立つ) 機能である「カプセル化」について、その概要とメリットを説明してみたいと思います。
成田
"Array クラスを作る" シリーズの 4回目です。
今回は、各種演算子の定義について解説します。
過去の内容については、以下の記事を参照してください:
成田
最近 3ds max から独自形式を吐き出すエクスポーターを作成しているのですが、その際 3d, xfile, maxsdk の仕様など学ぶことがいろいろありました。
今回はその備忘録的な内容です。
まずは独自形式の前に一般的に知られているファイル形式を出力したいと思いました。
そこで、アニメーションを設定できるファイル形式で一般的 (?) なものとしてX形式があります。
こちらは前々回のコラムでも書いたように非常に多数の方言があり、その構造解析の方法も様々です。
しかしながら必要な情報は入っている (エクスポーターによっては) こと、テキスト形式で出力が可能であるので、内部を理解しやすい利点があります。
なので、まずは情報が使いやすい形式を探してみます。
松浦
前回予定していましたFBX形式からメッシュやマテリアル情報を抜き出す方法ですが、FBXSDK のドキュメントがあまりにも (以下略) であるため、断念しました。
簡単に理由をあげますと、クラスの説明がたったの英語5単語であったり、関数名が Get なのに説明が Set だったりとすさまじいものがあります。
ですので今回はFBXはおいておき、モーションキャプチャデータであるBVHファイルの利用方法を考えてみたいと思います。
これに対応できるMax,Mayaプラグインを近日に書きたいと思っています。
既に合成を終えたわけではありませんので、間違っている部分があるかもしれません。
その部分は合成が終わり次第、プラグインのソースコードと一緒に公開したいと思います。
松浦
動的型付け (スクリプト) 言語では、データ型のチェックが実行時にしか行われないため、プログラムの妥当性検証・デバッグといった作業が困難になります。
例えば、Ruby でプログラムを書いていて、次のようなバグに悩まされたことのある人は多いのではないでしょうか。
- Integer オブジェクトを参照しているべき変数が、他の型のオブジェクトを参照している。
- そのオブジェクトが「いつ」「どこで」代入されたものなのか分からない。
この手のバグは、問題の発生 (不正な型の代入) と発覚 (エラーの発生) の位置が離れてしまうので、非常に厄介。
発生箇所を絞り込むのが難しいため、プログラムを広範囲に渡って見直すハメになります。
成田
前回のGPUでレイトレーシングを並列処理についてもう少し補足したほうがよさそうでしたので、補足します。
今回はGPUのジョブの単位、メモリの扱い、画面分割の割り当てを図を用いて説明します。
松浦
以前やったGPUでレイトレーシングを並列処理の続きとして、今回はGPUでレイトレーシングを行いたいと思います。
レイトレーシングはリアルな画像を作り出す反面、計算に大変時間がかかることはよく知られています。
その処理をいかに高速化できるか?というのが今回のコラムの目的です。
松浦
前回は、副作用である変数代入という概念が純粋関数型言語である Haskell には存在しないということを書きました。
今回と次回は、そのような変数という概念を純粋関数的に模倣した Haskell の State モナドについて書こうと思います。
今回は、State モナドの説明の前段階として、ローカルスコープを用いた変数代入の模倣についてです。
齋藤
C言語では、ローカル変数の宣言を関数の始めに宣言する必要がありました。
これを習慣としているのか、C++ でもローカル変数・オブジェクトの宣言を関数の開始位置で行う人がいます。
//分散を求める関数 (C++スタイル宣言)
int variance(const Array<double>& ad){
assert(ad);
double dSum =0;
int i;
for (i=0; i<ad.size; ++i) dSum +=ad[i];
double dAvr =dSum/ad.size;
double dSumSq =0;
for (i=0; i<ad.size; ++i){
double dTmp =ad[i] - dAvr;
dSumSq +=dTmp*dTmp;
}
return dSumSq/ad.size;
} |
//分散を求める関数 (Cスタイル宣言)
int variance(const Array<double>& ad){
assert(ad);
double dSum =0;
double dSumSq =0;
double dAvr;
double dTmp;
int i;
for (i=0; i<ad.size; ++i) dSum +=ad[i];
dAvr =dSum/ad.size;
for (i=0; i<ad.size; ++i){
dTmp =ad[i] - dAvr;
dSumSq +=dTmp*dTmp;
}
return dSumSq/ad.size;
} |
それでも動作には何の支障もありませんが、変数の「宣言」と「実際に使われ始める位置」が離れてしまうため、プログラムがやや読みづらくなります。
また、上例のCスタイルの方では、2番目のループ内でのみ使われる変数 dTmp のスコープが関数全体に及んでしまうのも、あまり良い状況ではないでしょう。
しかし、C++ が変数・オブジェクトの宣言を関数の任意の位置で行えるようになっているのは、このような可読性に関する (些末な) 問題のためだけではありません。
これには、C にはなかった「クラス」「オブジェクト」が大きく関係しています。
成田
昔書いたプログラムをもう一度使えないかと考えることは、多くのプログラマにとって (そしてエントリのネタを探す私にとっても) 重要なことです。
今日は、過去の遺産を有効活用して楽に問題を解いた事例を紹介しましょう。
UVa Online Judge の 10012番、How Big Is It? です。
田山
プログラマは自分でコードを書くことができるだけでなく、他人が書いたコードを (ある程度は) 読むことができなければなりません。
その際に最もゲンナリさせられるのが、コメントが全くないソースコードです。
腕の立つプログラマのコードというのはきちんと構造化されいるため、コメントに頼らずとも作者の意図をきちんと読み取ることができます。
しかし、そのようなコードを書くプログラマは、コメントもきちんと付けるもの。
むしろ、初心者や読みづらいコードを書くプログラマほど、コメントも疎かになる傾向があるようです。
彼らが自分のソースコードにコメントを付けない理由として挙げるものとしては、
時間がなかったから。(急いで修正したから。)
公開するつもりのないコードだから。
コメントを付けると、コメントに頼ってコードを読むようになってしまうから。
コメントを入れるのと、素人のコードっぽく見えるから。
コードの可読性は品質には影響しない。可読性をあげて良いことがあるのか?
などなど……。(いずれも、実際に言われたことがある。)
人それぞれに事情や主義主張があるのは分かりますが、正直「そのコードの保守運用をさせられるこっちの身にもなってくれ」と言いたくなります。(そして、実際に言っています。^^;)
成田
最近よく名前を聞くようになった Scala という言語で遊んでみました。
齋藤
二次元座標上の2点間の距離を求めたいとき、複素数の絶対値を求めたいとき、その他いろいろなときに x = √(a2 + b2) という式を使います (これを pythagorean additionと言うそうです)。
これを実装するとき、
という文がよく使われますが、この文を無闇に使っていると、あまり嬉しくない事態を引き起こすことになります。
田山
|
| 日 |
月 |
火 |
水 |
木 |
金 |
土 |
| |
|
|
|
1 |
2 |
3 |
| 4 |
5 |
6 |
7 |
8 |
9 |
10 |
| 11 |
12 |
13 |
14 |
15 |
16 |
17 |
| 18 |
19 |
20 |
21 |
22 |
23 |
24 |
| 25 |
26 |
27 |
28 |
29 |
30 |
31 |
| |
|
|
|
|
|
|
|