2008年8月8日星期五

C 函數的新發現

以往如需要從程式的主迴圈讀取鍵盤的輸入去決定是否退出程式, 我會用另一個執行緒去乎叫 std::cin 或 getchar(), 因為它們都是阻塞 (blocking) 的.

原來我一直忽略了 kbhit() 的存在! 有了它, 以上的問題就可簡化為:

#include

int main() {
while(true) {
// Poll the console without blocing, return true if there is
// a keystroke waiting in the buffer.
if(kbhit()) {
if(getchar() == 'q')
break;
}

// Do something usefull
// ...
}
return 0;
}

可惜 kbhit() 不是標準 C 裡的成員, 在若干平台上我們得自行實踐. 以下編碼出於這裡

#include <sys/select.h>

int kbhit(void)
{
struct timeval tv;
fd_set read_fd;

/* Do not wait at all, not even a microsecond */
tv.tv_sec=0;
tv.tv_usec=0;

/* Must be done first to initialize read_fd */
FD_ZERO(&read_fd);

/* Makes select() ask if input is ready: 0 is the file descriptor for stdin */
FD_SET(0, &read_fd);

/* The first parameter is the number of the largest file descriptor to check + 1. */
if(select(1, &read_fd, NULL/*No writes*/, NULL/*No exceptions*/, &tv) == -1)
return 0; /* An error occured */

/* read_fd now holds a bit map of files that are
* readable. We test the entry for the standard
* input (file 0). */
if(FD_ISSET(0, &read_fd))
/* Character pending on stdin */
return 1;

/* no characters were pending */
return 0;
}

2008年7月22日星期二

共享算術運算子

每當實作一些有關數學的類別 (如 Vector, Matrix, Point, Size 等...) 時,加減乘除等運算子都會時常出現。讓我們試試把共同的地方提煉成基類別:

template<int N>
class Tuple {
public:
Tuple operator+(const Tuple& rhs) const
{
Tuple result;
for(int i=0; i<N; ++i)
result.data[i] = data[i] + rhs.data[i];
return result;
}

float data[N];
};

class Vec3 : public Tuple<3> {
public:
Vec3(float x, float y, float z) {
data[0] = x; data[1] = y; data[2] = z;
}
};

int main() {
Vec3 v1(1, 2, 3);
Vec3 v2(4, 5, 6);
// Compilation error: v1 + v2 is returning Tuple but not Vec3
Vec3 v3 = v1 + v2;
return 0;
}

大家不用擔心那個回圈會為性能帶來負面影響,編譯器懂得把它優化 (我已在VC2008上證實了這一點)。
但由於運算子的返回型態出了問題,我們作出以下嘗試:

template<int N, class R>
class Tuple {
public:
R operator+(const Tuple& rhs) const
{
R result;
for(int i=0; i<N; ++i)
result.data[i] = data[i] + rhs.data[i];
return result;
}

float data[N];
};

class Vec3 : public Tuple<3, Vec3> {
public:
Vec3(float x, float y, float z) {
data[0] = x; data[1] = y; data[2] = z;
}
};

int main() {
Vec3 v1(1, 2, 3);
Vec3 v2(4, 5, 6);
Vec3 v3 = v1 + v2;
return 0;
}

非常好。不過還可以更好哩:

template<int N, class R, class U>
class Tuple : public U {
public:
R operator+(const Tuple& rhs) const
{
R result;
for(int i=0; i<N; ++i)
result.data[i] = data[i] + rhs.data[i];
return result;
}
};

struct _Vec3Union {
union {
struct { float x, y, z; };
float data[3];
};
};

class Vec3 : public Tuple<3, Vec3, _Vec3Union> {
public:
Vec3() {}
Vec3(float x_, float y_, float z_) {
x = x_; y = y_; z = z_;
}
};

struct _SizeUnion {
union {
struct { float width, height; };
float data[2];
};
};

class Size : public Tuple<2, Size, _SizeUnion> {
public:
Size() {}
Size(float w, float h) {
width = w; height = h;
}
};

int main() {
Vec3 v1(1, 2, 3);
Vec3 v2(4, 5, 6);
Vec3 v3 = v1 + v2;

Size s1(1, 2);
Size s2(2, 3);
Size s3 = s1 + s2;

return 0;
}

這可讓 Vec3 組成的 x, y 和 z 用方便的形式去存取。

儘管以上的提示未必有多大用途,還望它能加強大家對 C++ 的了解。

2008年7月14日星期一

尼采的神奇 Functor

尼采給我的題目...不如在此發表答案.


#include <iostream>

typedef int (*Functor)();

// Recursive type template that generate a
// compile-time list of Functor using inheritance
template<size_t N> struct Unit : public Unit<N-1> {
Unit() : mFunctor(&Unit<N>::fun) {}
static int fun() { return N; }
Functor mFunctor;
};

// Partial specialization to end the recursion
template<> struct Unit<0> {
Unit() : mFunctor(&Unit<0>::fun) {}
static int fun() { return 0; }
Functor mFunctor;
};

// Partial specialization to reduce recursive template complexity
template<> struct Unit<256> : public Unit<255> {
Unit() : mFunctor(&Unit::fun) {}
static int fun() { return 256; }
Functor mFunctor;
};

// Partial specialization to reduce recursive template complexity
template<> struct Unit<512> : public Unit<511> {
Unit() : mFunctor(&Unit::fun) {}
static int fun() { return 512; }
Functor mFunctor;
};

// A compile-time maximum count of Functor
static const size_t cMaxN = 768;

typedef Unit<cMaxN> List;
static const List cList;

// A function that return a Functor that return i.
// In other words, it selects from a list of compile-time functors
// base on the run-time parameter i.
Functor getFunctor(int i) {
{ // We have the assumption that Unit<> struct are packed tightly together in cList
typedef const char* byte_ptr;
byte_ptr functorAddres1 = byte_ptr(&(static_cast<const Unit<1>*>(&cList)->mFunctor));
byte_ptr functorAddres2 = byte_ptr(&(static_cast<const Unit<2>*>(&cList)->mFunctor));
(void)functorAddres1; (void)functorAddres2;
assert(functorAddres2 - functorAddres1 == sizeof(Functor));
}
assert(i <= cMaxN);
return *((Functor*)(&cList) + i);
}

int main() {
for(size_t i=0; i<=cMaxN; ++i) {
Functor f = getFunctor(i);
std::cout << f() << std::endl;
assert(f() == int(i));
}

return 0;
}


Win少, 我的答案正確嗎?

我認為最好的 Pdf 閱讀器



不知不覺 Adobe Acrobat Reader 已經出到第九代了.
在每一次更新的時候, 大家有沒有想過其實可能更有好的選擇呢?
我有一個好推薦, 它就是 Foxit Reader.

Foxit 的描繪品質和速度與 Acrobat 不分上下, 但記憶體用量和啟動時間就遠勝 Acrobat; 更不用安裝 , 細小可攜 (僅一個 5MB 的可執行檔).

以下的測試是基於網上的一篇文章:


Adobe Reader 9 Foxit Reader 2.2
啟動須時 (秒) 21 7
記憶體用量 74MB 25MB

縱然 Foxit 缺少某些(許多人都不常用的)功能, 它帶來的眾多好處立即使我把 100 多MB的 Acrobat Reader 從系統中撤除.

2008年7月2日星期三

程式語言效率大比拼

有天正在尋找腳本語言的時候無意間發現這個名為 The Computer Language Benchmarks Game 的網站.
那裡的 benchmark 數據覆蓋多達 76 種語言加編輯器的組合, 19 個試驗程式(大部分還有提供源始碼).
不過, 儘管那裡提供的資料準確無誤, 它所包含的參考價值仍然有限. 問題在於那些試驗程式都過於細小, 以及只有單一的功能. 在一個複雜的應用程式 (如 3D Game Engine), 有大量不同種類的資料需要處理, 因此記憶體的存取很容易成為瓶頸.
一個好例子就是 Java/.Net. 有數據顯示 Java/.Net 可以快過 C/C++, 不過當你走出試驗程式返回現實, 你會感到 Java/.Net 總是慢半拍的.

其實那網站的第一段就表明了:
Benchmarking programming languages?
How can we benchmark a programming language?
We can't - we benchmark programming language implementations.

How can we benchmark language implementations?
We can't - we measure particular programs!

2008年6月18日星期三

Resources loading

最近完成了漸進裝載系統 (Progressive resource loading) 的設計與實作.
現階段能夠以多緒形式載入 Jpeg 和 Png



要達到多緒, 首要分開載入和使用中的資料.
函式 load() 會把 istream 中的資料解碼到 loader 裏的私有緩衝區之中, 這時候 LoadingState 為 Loading.
當一定數量的資料已解碼而又可以顯示出來, load() 會返回 LoadingState = PartialLoaded, 這時候你可以乎叫 commit() 把 loader 中的緩衝抄到 resource 去.
請注意 load(), commit() 和 getLoadingState() 好有可能在不同的執行緒中執行, 因此 loader 裏的緩衝區和 LoadingState 成員都要用 Mutex 來保護.



有一點值得討論的就是 load() 這函式.
現在的設計須要用者不停地乎叫 load() 直到它返回 Loaded 或 Aborted. 但我曾經考慮過使用事件 (Event) 來通知用者現在的 LoadingState 從而只需乎叫 load() 一次. 接著當我試圖實作 cancel() 這函式時, 問題出現了; 我得建造另一個事件來控制 load() 內部的循環何時跳出.
實在不好, 複雜跳進來了...還要處理令人頭痛的多緒同步哩! 想起 Pull Xml 帶給我的啟示, 原來只要把 load() 內的循環交給用者來定義, 一切都簡單得多.
再細心的想, 如果用者希望一幅圖像載入了第一個漸進後便去開始載入另一幅圖像的話, 用事件的作法是行不通的 (或變得異常複雜). 所以這個新的設計不止簡單, 還非常靈活. 我又一次體會到什麼是 Less is more, There is No CODE that is more flexible than NO Code!.

2008年6月11日星期三

C++ friend class

有些時候我們會利用 C++ 中的 friend 關鍵字來設定類別的好友.
但可惜一個 friend 宣告只會對一個類別產生作用:

class Texture {
public:
friend class IResourceLoader;
uint width() const { return mWidth; }
uint height() const { return mHeight; }
private:
uint mWidth;
uint mHeight;
};

class IResourceLoader {};

class JpegLoader : public IResourceLoader {
void load() {
// ...
mTexture.mWidth = 128; // Compiler error!!
}
};

以上的 Texture 類別有成員 mWidth 和 mHeight, 它們都只應由 IResourceLoader 的實作類別所能更改. 幸好有 Inner class 可以幫到我們:

// Texture.h
class Texture {
public:
friend class IResourceLoader;
uint width() const { return mWidth; }
uint height() const { return mHeight; }

// We declare a templated inner class here (but not defined yet)
template class PrivateAccessor;

private:
uint mWidth;
uint mHeight;
};

class IResourceLoader {};

// JpegLoader.cpp
// Define Texture::PrivateAccessor here, access what ever you want
template<>
class Texture::PrivateAccessor {
public:
static size_t& width(Texture& texture) {
return texture.mWidth;
}
static size_t& height(Texture& texture) {
return texture.mHeight;
}
};
typedef Texture::PrivateAccessor Accessor;

void JpegLoader::load() {
// ...
Accessor::width(mTexture) = 128; // Ok! no problem
Accessor::height(mTexture) = 128;
}

你可能會認為這樣做等同把所有成員變為公開 (public), 事實上這個方案的精神在於迫使程序員清楚自己正在做什麼, 而不是錯誤地更改了變數.