#
前段時間讀了一本書《C++沉思錄》,偶有感,摘錄如下:
模板和泛型算法(摘自 《c++沉思錄》):
一個特例:
1.假設我們希望從整數數組中找到第一個等于某給定值的元素.編寫如下代碼:
const int*
find1(const int* array, int n, int x)
{
const int *p = array;
for( int i = 0; i < n; i++)
{
if(*p==x)
return p;
++p;
}
return 0;
}
2.泛型化元素類型:
用類型T來表示整型等,適當的時候可以把const也包含在T中,得到如下函數.
template<class T>
T* find2(T* array, int n, constT& x)
{
T* p = array;
for(int i=0; i<n; i++)
{
if(*p==x)
return p;
++p;
}
return 0;
}
3.推遲計數.
為了避免預先知道有多少個元素,我們改變函數,使它接受指向第一個元素和最后一個元素之后元素的指針.
template<class T>
T* find3(T* array, T* beyond, constT& x)
{
T* p = array;
while(p!=beyond)
{
if(*p ==x)
return x;
++p;
}
return 0;
}
用!=而不用<來判斷循環結束并不是偶然.從某種角度來說,兩者沒有區別,如果find3的輸入有意義,則p就小于beyond,直到它們相等為止.但是,由<加以總體排序的類型通常也能用!=來進行比較.另一方面,考慮一下我們以后可能會用到來代替指針的類型,他們可以很好地定義!=,但不一定能定義<.此時,使用<就是一個不合理的假設.
另外,我們還假設了,0可以轉換成一個與其他所有的值不同的指針值.我們稍微做一點改變,以避免這種假設:如果程序中要找的值沒找到,它就返回beyond而不是0.
template<class T>
T* find4(T* array, T* beyond, constT& x)
{
T* p = array;
while(p!=beyond)
{
if(*p ==x)
return x;
++p;
}
return beyond;
}
因為程序要么返回適當的T*, 要么返回beyond.故程序代碼可以被修改如下:
template<class T>
T* find5(T* array, T* beyond, constT& x)
{
T* p = array;
while(p!=beyond && *p != x)
++p;
return p;
}
4.地址的獨立性
到目前為止,我們還是依賴于傳遞來的指針,該指針要指向要查找的數據的開頭.但是如果仔細分析一下,會發現我們只依賴于指針的某些保留特性:
1)可以把指針當參數接收,并把它們作為結果返回.
2)可以比較指針是否相等.
3)可以解除引用,以便得到值:*p.
4)可以遞增,以指向下一個元素.
只要符合上述條件的類型即可,不一定是指針類型.假設把T*作為模板參數,我們就取消了對指針的依賴:
template<class P,class T>
T* find6(P start, p beyond, constT& x)
{
while(start !=beyond && *start != x)
++start;
return start;
}
我們已經完全剔除了函數中關于具體類型的信息.根本沒有要求p是指針,只要求p滿足上述的四個特性.
在C++中,通過提供構造函數、析構函數來對處理資源的獲取、釋放。
通過C++的這種機制,我們可以很方便地處理C++中的加鎖同步機制。把鎖對象作為Guard對象的一個成員(m_lock),然后在Guard對象的構造中對m_lock進行加鎖:m_lock.acquire(),在Guard對象的析構函數中進行解鎖:m_lock.release()。先給出代碼實例如下:
template <class T>
class Guard
{
public :
Guard(const T & lock);
virtual ~Guard();
private:
const T & m_lock;
};
template <class T>
Guard<T>::Guard(const T & lock) :
m_lock(lock)
{
m_lock.acquire();
}
template <class T>
Guard<T>::~Guard()
{
m_lock.release();
}
我們可以在應用程序中這樣使用它:
void testFunc(.....)
{
Guard<MutexWrapper> guard(mutex);
...
}
在剛進入函數testFun(...),創建guard對象,并自動對mutex進行加鎖,對特定數據(resource)進行保護。當應用離開testFunc函數模塊時,根據guard對象的作用域和生命周期,此時guard對象的析構函數將被調用,因此將自動對mutex進行解鎖。在此之后應用的其他線程將可以訪問以前被mutex進行保護起來的資源。
利用上面的方法,我們可以包對資源的同步訪問和訪問控制交給C++的編譯器,而不需要進行人工干預,從而減輕應用開發人員的工作負擔。
C++中類包含三種成員訪問說明符:public, private 和 protected.
在程序能訪問類對象的任何地方都可以訪問任何在成員訪問說明符public后面聲明的數據成員和成員函數.成員訪問符private后面的數據成員和成員函數只能由該類的成員函數或友元訪問.基類的protected成員只能被基類的成員和友元已及派生類的成員和友元訪問.
在C++中還存在三中繼承方式:public, private, protected.
對于它們的論述可以在任意一本關于C++的書中都可以找到.大家對public繼承都比較熟悉.但我們偶爾也會看到private繼承.private繼承時基類中的public,private成員變成派生類中的private成員.基類中的private成員在派生類中隱藏.
這里簡單介紹一下以下兩種情況的異同:
(1)B private 繼承A
(2)A是B的一個私有成員的異同.
相同點:A的接口(public 成員函數)都只對B開放,不對外開放.
不同點:在(1)中A的public, protected成員都為B的private成員,B的成員函數可以直接訪問.在(2)中A的成員都不是B的成員,并且B不能訪問A的protected成員,要訪問A的public成員也要借助A的對象.
下面再講一些編譯器在構造類時所采取的缺省操作:
1.如果類沒有聲明構造函數,編譯器會聲明一個默認構造函數.
2.如果沒有聲明拷貝構造函數,編譯器會隱式地生成一個.
3.如果沒有聲明賦值操作符,編譯器會隱式地生成一個.
4.如果沒有聲明析構函數,編譯器會隱式地生成一個.
隱式生成的函數都是public的.
如果接受一個副本是有效的行為,就該聲明并定義拷貝構造函數和賦值操作符.如果接受一個副本是禁止的,你可以將拷貝構造函數和賦值操作符聲明為private,并且不實現它們,這樣可以阻止編譯器生成缺省的操作,從而防止客戶復制類對象.
下面是代碼實例:
class test{
};
該類中不包含任何成員,也沒聲明任何方法.編譯器會缺省生成下列方法:
test::test()
{
}
test::~test()
{
}
test::test(const test& rt)
{
...
}
test& test::operator=(const test& rt)
{
...
}
這些方法都是public的.
如果想阻止編譯器生成缺省的拷貝構造函數,和賦值操作,可以進行如下操作:
class test{
private:
test(test& rt); // 該方法被定義為private,并且不被實現.
test& operator=(test& rt); // 該方法被定義為private,并且不被實現.
};
在應用開發構成中,我們經常在程序中加入一些打印語句,來對程序的執行流進行跟蹤.在C或C++中可以利用下列語句來實現:
(1)
printf("enter %s\n",(char *)funcName);
或
cout<<"enter "<< s_funcName << endl;
但這樣處理有點不足,就是該語句只輸出到標準輸出上,我有時希望這些輸出被定向到特定文件,輸出成日志.為此,我們可以把這些函數進行包裝,把輸出流ostream(標準輸出或文件輸出)作為包裝函數的一個參數:
(2)
printWrap(ostream out,format, args);
注:此處的args, format表示要輸出的參數和相應的參數格式.
當然我們還可以對它進行進一步的改進:在該函數中,加入預定以的符號常量__LINE__(當前源代碼行的行號,為整數常量),__FILE__(假定的源文件名,某個字符串).這樣我們可以知道程序運行到了那個源文件,并且那一行.
現在(2)中的處理方式比(1)中處理方式已經有明顯的改善了.
但這種方式還稍微有點不足.當我們想要跟蹤一個函數的執行,即知到執行流進入某函數,何時離開某函數時,這種處理方式有點不足.每個函數都有一個入口,但可能有多個出口,這樣就需要在每個入口和出口處加上printWrap(ostream out,args)語句,并且在C++中,當執行流遇到異常退出該函數時,可能有些printWrap語句并沒有被執行,從而沒有輸出記錄.
為此,我們可以對(2)進行進一步改進.我們可以設計一個類,在該類對象的構造函數,析構函數中進行輸出.在函數的入口處,調用對象的構造函數進行輸出;在函數的出口處,或異常退出時,調用對象的析構函數進行輸出.
我們可以把該類簡單總結如下:
(3)
class Trace{
public:
Trace(int iDebugLevel,ostream out, format,args) { cout <<"Hello\n";}
~Trace() { cout << " Goodby\n";}
int getDebugLevel();
private:
...
int iDebugLevel;
ostream m_out;
};
注: 我們可以用printWrap(..)替換cout << ....。printWrap中的輸出流在Trace的構造函數中傳到Trace實例中,并被保存。
我們還可以對它進行一點改進,以提高它的性能。因為采用上面的對象。則每次都會進行輸出或進行日志記錄.我們可以通過構造函數在Trace的實例中,設置一個iDebugLevel變量和ostream。并在系統中設置一個統一的debugLevel.在每次進行輸出時進行iDebugLevel, debugLevel比較,如果iDebugLevel <= debugLevel, 則進行輸出,否則則不進行輸出.
前段時間,碰到了C,C++混合編程的需求,經過努力,順利解決問題.現把這方面的知識做一下簡單的總結:
1.當C++文件要用到C語言中的函數代碼時,采用下屬方法即可:
在C++中的.h文件或.cpp文件中加入下列代碼,
#define LINT_ARGS 1
extern "C" {
#include "system.h"
}
然后在代碼中直接調用這些函數即可.
注解:
1.1 LINT_ARGS 1表示在檢查C語言中的函數原型時,要對函數原型的參數進行檢查.
1.2. "{ }" 中包含的頭文件為C語言中的頭文件.
1.3.extern "C" 告訴鏈接器,這些頭文件中的函數被當做C語言中的函數來處理.
下面以一個實例加以說明:
下面為一個C語言的頭文件(sysgct.h):
#ifdef LINT_ARGS
int GCT_send(unsigned int task_id, HDR *h);
......
#else
int GCT_send();
......
#endif
~
in file MapBaseReq.cpp 文件中
#include ....
extern "C"
{
#include "sysgct.h"
}
void
MapBaseReq::sendPdu(const BasePdu_var& thePduP)
{
...
if (GCT_send(m->hdr.dst, (HDR *)m) != 0)
{
relm((HDR *)m);
SETERR1(errSWErrorMsg, "sendPdu(): GCT_send() Failed");
}
...
}
2.當C文件要用到C++語言某個類中的方法時,可以采用下列方法:
2.1 在cpp文件中用下列方式定義函數:
extern "C" returnType FunName(parameters list).
2.2 然后在相應的頭文件中進行聲明:
extern returnType FunName(parameters list);
2.3 在相應的.c文件中包含該頭文件,然后直接利用相應的函數即可.
下面給出實例.
2.4 cpp文件
#include <iostream>
#include <iomanip>
#include "TTDebug.h"
using namespace std;
extern "C"
{
#include "Utility.h"
}
static int display_hex_buffer(unsigned char* buffer, unsigned char* ptr,int len);
extern "C" void tDebug_traceFunc(int level, char* trace)
{
TDEBUG_TRACEFUNC(level,trace);
}
extern "C" void nDebug(int level, unsigned char* args, int iLen, int cid)
{
unsigned char buf[512];
if(0 != TTDebug::instance() && TTDebug::instance()->isTTDebugOn(level))
{
/* Check whether the current thread already holds the mutex lock */
LockGuard<MutexWrapper> guard(TTDebug::instance()->mutex());
TTDebug::instance()->traceStart(level, __FILE__, __LINE__);
memset(buf,0,512);
display_hex_buffer(buf,args,iLen);
TTDebug::instance()->outStream() << "Send Msg(0-0x" << setw(4) << setfill('0') << hex << cid <<"):0x" << buf;
TTDebug::instance()->traceEnd();
}
}
2.5 .h 文件
#ifndef __UTILITY_H
#define __UTILITY_H
extern void tDebug_traceFunc(int level, char* trace);
extern void nDebug(int level, unsigned char* args,int iLen, int cid);
#endif
2.6 cpp文件中定義的函數在c文件中調用實例
在test.c文件中:
...
int ctu_ent(msg,pInt)
MSG* msg;
int *pInt;
{
tDebug_traceFunc(10,"ctu ctu_ent");
HDR *h;
MSG *m;
...
}
...
在運用JAVA,C++等面向對象的語言進行開發的時候,不可避免地要用到繼承.即從一個父類(P)派生出相應的子類(C).在開發應用的時候,我們可以僅從單個類的角度來考慮繼承或派生.但是我們可以進一步對它進行引申.比如我們可以用基類(或純抽象類,JAVA中的接口)來開發處理某類業務的抽象架構或平臺,然后針對具體的應用,我們派生出相應的派生類,讓它們來完成具體業務的具體邏輯.
在C++中,基礎架購用基類寫就,但具體業務邏輯用派生類來實現.為了做到這一點,我們必須在架構中指向基類對象的指針(->操作符),并且定義相應的虛函數(或純虛函數).這樣實現程序的動態多態.這樣實現既滿足了面向對象設計的OCP原則(open-close principle).
在基礎架構中可能還包含保存基類指針的容器,這些指針可能后來所賦之值是派生類的指針,并且考慮到對象的生命周期,這些對象應該是通過NEW操作在heap上生成的對象,而不是在stack上保存的局部對象.為了保證這些對象的自動銷毀,不需要應用開發人員的人工干預,這些保存在容器中的指針最好是含有基類指針的智能指針SmartPointer,或者說是代理類.SmartPointer是代理類中的一種.
根據前一篇文章的分析,在應用對對象指針的處理,采用了智能指針.但指向基類(P)的智能指針(SmartPp)與指向子類(C)的智能指針(SmartPc)不是父類與子類的關系,它們應該是同一類的不同實例.因此還應該對智能類定義如下操作,使之滿足轉型要求:
(1)從智能指針中獲取指向對象指針的能力.
(2)根據指向對象的指針生成智能指針的能力.
滿足這兩點,我們就可以從SmartPc中獲取pC(指向子類的指針),然后把它轉型成pP(指向父類的指針),然后再根據pP生成SmartPp,然后保存在基礎架構的容器中.在實際應用的過程中,會用到指向父類的指針,但此時它實際上是指向子類的指針,在程序運行的過程中,將用到動態多態性,即虛函數來處理相應的應用.
BTW(順便說一下),因為一般說來容器(MAP,vecotr,或數組)只能保存一種類型,另外又要用到運行時的多態,最好保存指向基類對象的指針,而不直接保存對象,否則子對象將被切割,只保留父類部分,其余將被丟棄.另外為減少對對象管理的負擔,最好在容器中保存對象的代理對象.
1.淺論C++中的智能指針(Smart Pointer)
簡單地講,智能指針是用一個對象來對指針進行建模,使之具有指針的特性,跟指針具有相同含義的->,*操作.并且通過對象的構造函數(獲取資源),析構資源(釋放資源)來對資源進行管理,從而減少程序員對通過new操作獲取到的對象的生命周期進行管理的負擔.
根據《Moden C++ Design》, 我們可以構造具有很多正交特性的智能指針。
1.1 C++中的智能指針與JAVA中的對象
前段時間跟朋友聊了些有關JAVA的東西,感覺上Java中的對象就是C++中的智能指針,但具有不同的資源釋放方式。在JAVA中,不能象C++中運用" A a;"語句聲明得到一個類(A)的事例a,而必須通過下列語句來獲得:Aa = new A.要在釋放a時,應用必需通知
GC(垃圾收集功能)來釋放該實例所占用的資源。當然,JAVA中的對象有一小點同C++中的職能智能不同,因為在C++中指針不具有"."操作符,故智能指針一般也不提供"."操作符,但在Java中都是通過"."操作符對對象進行操作的,不過我們可以把C++中職能指針的"->"操作符與
Java中的"."操作符進行類比。
1.2 引用計數型智能指針
在C++中有一種常用的智能指針是引用計數型智能指針:RCSmartPtr. 它的實現基理如下:
首先,存在RCObject,即存在一個對象,該對象提供引用計數接口。
另外,要存在指向RCObject的RCSmartPtr對象,在RCSmartPtr對象的構造過程中,把指向RCObject的指針作為參數傳入RCSmartPtr中。因此每增加一個RCSmartPtr對象,就多了一個指向RCObject的指針。RCSmartPtr可以通過調用RCObject的引用計數接口,增加RCObject
的引用計數。同樣的道理可以在RCSmartPtr對象的析構函數中調用RCObject的引用記數接口來減少RCObject的引用記數。
第三,在對RCObject的引用計數進行操作時對引用計數進行檢查,如果引用計數為0,則RCObject將摧毀本身,從而釋放該對象所占用的資源。
通過這種方式,我們就可以把對資源的管理交給機器來管理,解除了對人工的倚賴。