#
前段时间读了一本书《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将摧毁本身,从而释放该对象所占用的资源。
通过这种方式,我们就可以把对资源的管理交给机器来管理,解除了对人工的倚赖。