根據(jù)C++提供的構(gòu)造函數(shù),析構(gòu)函數(shù)我們可以實(shí)現(xiàn)在對(duì)象創(chuàng)建的時(shí)候和對(duì)象銷毀的時(shí)候根據(jù)我們的需要進(jìn)行一些輸出操作.我們可以在調(diào)用的函數(shù)中開(kāi)始的時(shí)候聲明這樣一個(gè)對(duì)象,用來(lái)跟蹤函數(shù)執(zhí)行的生命周期.
設(shè)計(jì)一個(gè)跟蹤類
class Trace{
public:
Trace() { std::cout << "Hello\n"; }
~Trace() { std::cout << "Bye\n"; }
};
然后在需要跟蹤的函數(shù)中使用這個(gè)類:
void foo {
Trace t;
// do something here
}
這樣會(huì)在函數(shù)開(kāi)始執(zhí)行的時(shí)候打印Hello
在函數(shù)結(jié)束的時(shí)候打印:Bye
.
這個(gè)簡(jiǎn)單的類在多個(gè)函數(shù)中使用的時(shí)候會(huì)出現(xiàn)不能區(qū)分是哪些函數(shù)的執(zhí)行過(guò)程的問(wèn)題,所以我們對(duì)他進(jìn)行一些修改.
class Trace{
public:
Trace(const char* p, const char* q): bye(q) { std::cout << p << "\n"; }
~Trace() { std::cout << bye << "\n"; }
private:
const char* bye;
};
在使用時(shí)就可以根據(jù)初始化的參數(shù)的不同進(jìn)行區(qū)分:
void foo() {
Trace t("begin foo", "end foo");
// do something here
}
在使用這個(gè)類時(shí)間長(zhǎng)了以后,我們會(huì)發(fā)現(xiàn)這個(gè)類還有三個(gè)地方可以進(jìn)行優(yōu)化:
- 打印的內(nèi)容一般都是以"begin"開(kāi)始,“end”結(jié)尾.
- 如果可以通過(guò)開(kāi)關(guān)控制是否要打印輸出會(huì)給我們帶來(lái)好處.
- 如果可以選擇輸出到哪里,就可以跟業(yè)務(wù)邏輯的輸出區(qū)分開(kāi)存儲(chǔ)了.
我們需要仔細(xì)區(qū)分下2和3.一般操作系統(tǒng)都提供一個(gè)輸出到/dev/null的類似功能,輸出到這里的輸出將全部被丟棄,也就是說(shuō):禁止輸出其實(shí)是輸出重定向的一個(gè)特例.
下面我們來(lái)想一想,這個(gè)輸出重定向的流應(yīng)該放在哪里.
- 使用全局指針的方式.Trace使用全局的輸出流指針進(jìn)行輸出.這樣會(huì)給用戶帶來(lái)使用的負(fù)擔(dān),有些需要打開(kāi)輸出,有些需要關(guān)閉輸出這樣的控制需要用戶很謹(jǐn)慎的操作.
- 將
ostream*
放在Trace
類中,當(dāng)作成員變量.這樣在Trace
對(duì)象創(chuàng)建的時(shí)候我們總要給一個(gè)ostream
的指針才行. - 使用
Trace
類進(jìn)行跟蹤的場(chǎng)景總是聚集在一起的.所以我們幾乎總是需要所有的Trace對(duì)象同時(shí)輸出到某個(gè)文件中.所以我們考慮新建一個(gè)類,用來(lái)管理一組Trace對(duì)象輸出到哪里.
class Channel {
friend class Trace;
ostream* trace_file;
public:
Channel(ostream* o = &cout): trace_file(o) {}
void reset(ostream* o) {trace_file = o}
};
class Trace {
public:
Trace(const char* s, Channel* c):name(s), cp(c) {
if (cp->trace_file) {
*(cp->trace_file) << "begin " << name << endl;
}
}
~Trace() {
if (cp->trace_file) {
*(cp->trace_file) << "end " << name << endl;
}
}
private:
Channel* cp;
const char* name;
};
這樣做Trace就可以在綁定了相應(yīng)的Channel對(duì)象后將內(nèi)容打印到Channel中的輸出流中.并且可以通過(guò)Channel的reset方法進(jìn)行調(diào)整.
創(chuàng)建死代碼
我們的Trace類對(duì)象每次創(chuàng)建的時(shí)候都會(huì)消耗資源,為了減少這些消耗,我們需要在代碼中添加一些死代碼,以讓編譯器識(shí)別出來(lái)進(jìn)而降低函數(shù)調(diào)用的開(kāi)銷.
static const bool debug = 0;
class Trace {
public:
Trace(const char* s, Channel* c): {
if (debug) {
name = s;
cp =c;
if (cp->trace_file) {
*(cp->trace_file) << "begin " << name << endl;
}
}
}
~Trace() {
if (debug) {
if (cp->trace_file) {
*(cp->trace_file) << "end " << name << endl;
}
}
}
private:
Channel* cp;
const char* name;
};
注意,如果debug是false的話,構(gòu)造函數(shù)中根本不會(huì)初始化cp
,name
這兩個(gè)成員.
生成對(duì)象審計(jì)跟蹤
上面只是討論了函數(shù)的跟蹤,我們也可以將Trace作為一個(gè)類的成員變量使用.進(jìn)而跟蹤類的創(chuàng)建和銷毀是否匹配,有沒(méi)有存在的內(nèi)存泄漏.
我們將跟蹤對(duì)象的Trace類實(shí)現(xiàn)為Obj_trace并且直接輸出到cout
.
class Obj_trace {
public:
Obj_trace():ct(++count) {
std::cout << "Object " << ct << "constructed" << std::endl;
}
~Obj_trace() {
std::cout << "Object " << ct << "destroyed" << std::endl;
}
Obj_trace(const Obj_trace& ):ct(++count) {
std::cout << "Object " << ct << "copy constructed" << std::endl;
}
Obj_trace& operator=(const Obj_trace&) {
return *this;
}
private:
static int count;
int ct;
};
int Obj_trace::count = 0;
比較特殊的是賦值運(yùn)算符的實(shí)現(xiàn),因?yàn)闆](méi)有新的對(duì)象產(chǎn)生,所以我們沒(méi)有賦值ct
.將ct
當(dāng)作每個(gè)對(duì)象唯一的編號(hào)看待.