2018年01月23日
《その252》 クラステンプレートの特殊化
クラステンプレートの特殊化
本ブログの《249》演習9-3 の問題文に ヘッダファイル Array.h のコードを記載してありますが、そのヘッダにある Array<>クラステンプレートを bool型に特殊化したのが、下記のコードです。
やや複雑でわかりにくいので、順を追ってプログラムを見ていきたいと思います。
先にプログラムを記載して、そのあと、プログラムの進行順に、動作の説明を書きます。
以下が、プログラムです。
// Array.h
template <class Type> class Array { };
// BoolArray.h
// template<> class Array<bool>
// 配列クラステンプレートArray<> を bool型に特殊化
#include <limits>
#include "Array.h"
template<> class Array<bool> {
typedef unsigned char BYTE;
static const int CHAR_BITS
= std::numeric_limits<unsigned char>::digits;
// bool型配列の要素数
int nelem;
// bool型配列を格納するためのBYTE型配列の要素数
int velem;
// BYTE型先頭要素へのポインタ
BYTE* vec;
// バイト型配列要素の必要数を算出
static int size_of(int sz) {
return (sz + CHAR_BITS - 1) / CHAR_BITS;
}
public:
// 該当バイト中の該当ビットへの参照を表すクラス
class BitOfByteRef {
BYTE& vec; // 参照先BYTE
int idx; // 参照先BYTE中のビット番号
public:
// コンストラクタ
BitOfByteRef(BYTE& r, int i)
: vec(r), idx(i) { }
// 該当ビットを bool型で返却
operator bool() const { return (vec >> idx) & 1U; }
// 該当ビットに true または false をセット
BitOfByteRef& operator=(bool b) {
if (b)
vec |= 1U << idx;
else
vec &= ~(1U << idx);
return *this;
}
};
// 添字範囲エラー
class IdxRngErr {
const Array* ident;
int index;
public:
IdxRngErr(const Array* p, int i) : ident(p), index(i) { }
int Index() const { return index; }
};
// コンストラクタ
explicit Array(int sz, unsigned int v = 0)
: nelem(sz), velem(size_of(sz)) {
vec = new BYTE[velem];
// 全要素を初期化
for (int i = 0; i < velem; i++)
vec[i] = v;
}
// コピーコンストラクタ
Array(const Array& x) {
if (&x == this) {
nelem = 0;
vec = NULL;
}
else {
nelem = x.nelem;
velem = x.velem;
vec = new BYTE[velem];
for (int i = 0; i < velem; i++)
vec[i] = x.vec[i];
}
}
// デストラクタ
~Array() { delete[] vec; }
// 要素数を返す
int size() const { return nelem; }
// 代入演算子
Array& operator=(const Array& x) {
if (&x != this) {
if (velem != x.velem) {
delete[] vec;
velem = x.velem;
vec = new BYTE[velem];
}
nelem = x.nelem;
for (int i = 0; i < velem; i++)
vec[i] = x.vec[i];
}
return *this;
}
// 添字演算子[]
BitOfByteRef operator[](int i) {
if (i < 0 || i >= nelem)
throw IdxRngErr(this, i);
return BitOfByteRef(
vec[i / CHAR_BITS], (i & (CHAR_BITS - 1))
);
}
// 添字演算子[]
bool operator[](int i) const {
if (i < 0 || i >= nelem)
throw IdxRngErr(this, i);
return
(vec[i / CHAR_BITS] >> (i & (CHAR_BITS - 1)) & 1U)
== 1;
}
};
// BoolArrayTest.cpp
// 配列クラステンプレートArray<bool>の利用
#include <iostream>
#include "BoolArray.h"
using namespace std;
// Array<bool>型配列の全要素を表示
void print_Array_bool(const Array<bool>& a) {
for (int i = 0; i < a.size(); i++)
cout << (a[i] ? '1' : '0');
}
int main() {
Array<bool> a(10);
a[2] = true;
cout << boolalpha << a[2] << '\n';
cout << "a = "; print_Array_bool(a);
cout << '\n';
}
上記のプログラムの最後にある BoolArrayTest.cpp が、bool型に特殊化したクラステンプレートを利用するプログラムです。
BoolArrayTest.cpp の main関数のコードを順に追っていきます。
◆Array<bool> a(10);
・コンストラクタによって、オブジェクト a が作られます。
・データメンバ nelem に、ビット要素数 10 が格納されます。
・データメンバ velem に、バイト要素数 2 が格納されます。
・この 2 は、メンバ関数 size_of が計算した値です。(10 + 8 - 1) / 8 = 2
・バイト要素用の配列記憶領域が動的に確保されて、その先頭要素へのポインタが vec に代入されます。
・初期値を与えなかったので、各ビット要素は 0 すなわち false で初期化されます。
◆ a[2] = true
・ Array
添字演算子関数 BitOfByteRef operator[](int i); が呼ばれます。
・ 引数 i は 2 なので、次に、BitOfByteRef のコンストラクタが、
BitOfByteRef(vec[2 / 8], (2 & (8 - 1))); ※
すなわち
BitOfByteRef(vec[0], 2);
のように、呼ばれます。
その結果、
vec[0]への参照と、ビット番号 2 が、BitOfByteRef のデータメンバにセットされます。
・ その状態、つまり、参照先 vec[0]、参照先ビット番号 2 という状態の
BitOfByteRefクラスオブジェクトのコピーが、a[2]の返却値です。
コピーとは言っても、データメンバ vec は参照なので、
Array
a[2] = true により、そこに true がセットされることになります。
vec[0] のビット番号 2 のビットに 1 がセットされるわけです。
もちろん、このとき使われる代入演算子「 = 」は、
BitOfByteRefクラスのメンバ関数 BitOfByteRef& operator=(bool b); です。
◆ cout << boolalpha << a[2];
・ ここでも、添字演算子 BitOfByteRef operator[](int i); が呼ばれます。
先程と同じく、BitOfByteRefクラスオブジェクトのコピーが、a[2]の返却値です。
その返却値は、メンバ関数 operator bool() const; によって、true または false
の形で出力されることになります。
◆ cout << "a = "; print_Array_bool(a);
・ print_Array_bool関数の仮引数が const Array
const用の添字演算子 bool operator[](int i) const が呼ばれます。
◆【付け足し】※印の 2 & (8 - 1) の計算について
私のPCでは、1バイトが 8ビットなので 8 で説明します。
00001000 … 8
00000111 … 8-1
なので、
◆ 2 & (8 - 1) は
00000010 … 2
&) 00000111 … 8-1
-------------
00000010 … 2
◆ 10 & (8 - 1) は
00001010 … 10
&) 00000111 … 8-1
-------------
00000010 … 2
◆ 8 & (8 - 1) は
00001000 … 8
&) 00000111 … 8-1
-------------
00000000 … 0
この計算では、右から 4番目の桁以上はすべて 0 になってしまいます。
つまり、この計算は、8で割ったときの余りを求めています。
なので、
2 & (8 - 1)
は、
2 % 8
としても同じ結果になります。
この記事へのコメント
コメントを書く
この記事へのトラックバックURL
https://fanblogs.jp/tb/7230881
※ブログオーナーが承認したトラックバックのみ表示されます。
この記事へのトラックバック