
不知道是腦殘還是怎麼來著的,最近在開發衍生自QWidget類別的GUI應用程式的時候,功能不僅好像沒動作,還在關閉視窗時遭遇了詭異的執行時期錯誤,而且程式本來都還執行得好好的,直到後來類別越寫越多時問題才浮現出來,囧。
這個問題耗費了我不少時間,信心也為之動搖,甚至同樣的程式碼邏輯還更換了好幾種不同的寫法,最後才發現原來是因為:在衍生QWidget類別時,沒有傳遞適當的參數給基礎類別QWidget的建構式去進行正確的初始化。
我是無意間才發現自己在衍生類別的建構式當中遺漏了這個步驟,信手翻閱《C++ GUI Programming with Qt 4,2/e》和《C++ Primer中文版,3/e》,果然在裡頭找到了我想知道的理論部分,前者在第16頁,後者在第48頁。
一般而言,我們在開發GUI應用程式時,比方說撰寫一個繼承自QWidget的簡單畫面,其類別定義會長得像這樣:
class Window : public QWidget
{
Q_OBJECT
public:
Window(QWidget *parent = 0);
~Window();
private:
bool isActive;
int count;
};
然後在實作其建構式的時候,會在成員初始化序列(member initialization list),也就是以冒號(:)引導的那個段落裡面,將parent參數傳遞給基礎類別(此處即QWidget)的建構式,透過此機制來進行基礎類別的初始化。像這樣:
Window::Window(QWidget *parent)
: QWidget(parent)
{
}
附帶說明,成員初始化序列的作用顧名思義就是可以透過小刮號((...))來對類別的資料成員進行初始化,並且以逗號(,)分隔開來,例如:
Window::Window(QWidget *parent)
: QWidget(parent), isActive(false), count(0)
我就是因為少做了這個「在衍生類別的建構式的成員初始化序列裡面,將相關資訊傳遞給其基礎類別以便順利進行初始化」的步驟,才會導致遭遇執行時期的錯誤。理由是:
基礎類別的建構式並不會被衍生類別繼承下來,因此必須透過衍生類別的成員初始化序列來獲得參數,以便進行所需的初始化動作(如果有的話)。換言之,針對本例,基礎類別QWidget未被適當呼叫建構式來進行初始化,是導致程式遭遇執行時期錯誤的原因所在。
檢視Qt 4.5裡面的QWidget和QObject類別原始碼,都可以發現這個步驟是很重要的,因為它們的建構式確實需要一些參數來協助完成其初始化。摘錄如下:
QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
: QObject(*new QWidgetPrivate, 0), QPaintDevice()
{
d_func()->init(parent, f);
}
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
Q_D(QObject);
qt_addObject(d_ptr->q_ptr = this);
d->threadData = (parent && !parent->thread()) ?
parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0,
d->threadData))
parent = 0;
setParent(parent);
}
所以這裡可以歸納出一個非常重要的結論,就是:在某些類別裡面,即使衍生類別似乎並不需要建構式來做些特別的初始化工作,但是最好還是別忘了撰寫其建構式,因為可能至少還是要在成員初始化序列裡面,將必要參數(如果有的話)傳遞給基礎類別的建構式,否則在某些情況下,可能將會因此而導致程式發生錯誤。相反地,解構式就沒這樣的考量了,如果沒有特別的資料成員需要被釋放的話就可以不寫,反正衍生類別的物件在解構時會自動去呼叫基礎類別的解構式來歸還系統資源。
哇,C++與Qt應用程式框架裡面的類別庫真是博大精深呀。