1. COM是一个更好的C++

看COM本质论做的总结

1.1 软件分发和C++

class FastString {
	char* m_psz;
public:
	FastString(const char *psz);
	~FastString();
	int Length(void) const;
	int Find(const char *psz)const;
};
#include "faststring.h"
#include 

FastString::FastString(const char *psz)
	:m_psz(new char[strlen(psz) + 1])
{
	strcpy(m_psz, psz);
}
FastString::~FastString()
{
	delete[] m_psz;
}
int FastString::Length(void) const
{
	return strlen(m_psz);
}
int FastString::Find(const char *psz) const
{
	// O(1)
	return 0;
}

假如一个faststring.obj 需要16MB的空间,有三个应用程序ABC都要用到这个类,的方法,那虚拟内存就需要48MB;

另一种情况是,一旦类库的厂商发现了FastString类中的缺陷,我们只能全部重新编译ABC三个应用程序;

1.2 动态链接和C++

解决上面的问题,就是将FastString独立为dll类

#ifndef EXPORT_IMPORT_API
	#ifdef MAKING_LIBRARY
		#define EXPORT_IMPORT_API __declspec(dllexport)
	#else
		#define EXPORT_IMPORT_API __declspec(dllimport)
	#endif
#endif

class EXPORT_IMPORT_API FastString {
	char* m_psz;
public:
	FastString(const char *psz);
	~FastString();
	int Length(void) const;
	int Find(const char *psz) const;
};

库的编译add_definitions(-DMAKING_LIBRARY)

应用程序调用不需要define MAKING_LIBRARY

1.3 C++和可移植性

到此,以上用dll的形式,你将面临C++的基本弱点: C++缺少二进制一级标准。

由于FastString 引入库lib和引出符号dll使用了创建该dll的编译器(比如Gnu C++)的名字改编方案,所以使用其它编译器(比如Borland C++)产生的客户将无法与引入库lib成功链接。

消除名字改编现象的经典技术是使用 extern “C”, 但是这项技术对于FastString这种情况并没有用,因为它引出的是成员函数,而非全局函数。

有一项技术能够减轻这个问题,就是在客户的链接器上,使用模块定义文件(Module Definition File, 通称为DEF文件)

1.4 封装性和C++

假设此时你需要优化一下FastString类,增加了一个成员变量 int m_len(如下)

class EXPORT_IMPORT_API FastString {
    const int m_len; //add
	char* m_psz;
public:
	FastString(const char *psz);
	~FastString();
	int Length(void) const;
	int Find(const char *psz) const;
};

虽然类的公共接口没有变,但实际上sizeof(FastString)发生了变量,由原来的4变成了8。如果直接替换FastString.dll,这样新增的4字节内存就可能被应用程序其它地方占用,造成异常。

所以不得不重新用新的FastString.lib重新编译应用程序ABC。 由于这种耦合性,以及上一节的到的编译器和链接器的不兼容性,“简单地把C++类的定义从DLL中引出来”这种方案并不能提供合理的二进制组件结构。

1.5 把接口从实现中分离出来

faststringitf.h

class EXPORT_IMPORT_API FastStringItf {
    class FastString;
    FastString *m_pThis;
public:
	FastStringItf(const char *psz);
	~FastStringItf();
	int Length(void) const;
	int Find(const char *psz) const;
};

faststringitf.cpp

#include "faststring.h"
#include "faststringitf.h"

FastStringItf::FastStringItf(const char *psz)
	:m_pThis(new FastString(psz))
{
	assert(m_pThis != 0);
}
FastStringItf::~FastStringItf()
{
	delete m_pThis;
}
int FastStringItf::Length(void) const
{
	return m_pThis->Length();
}
int FastStringItf::Find(const char *psz) const
{
	return m_pThis->Find(psz);
}

这样,客户只用包含FastStringItf类的头文件就行,FastStringItf构造函数中的new操作符的调用也要被重新编译,以确保总是分配足够的内存。而且客户永远不会包含实现类FastString类的定义,这使FastString实现者非常灵活。解决了1.4中所存在的问题。

但这样有个坏处,就是如果公有函数比较多的大型类库,光编写这些传递过程就可能非常冗长,也增加出错的可能性,也增加开销。

1.6 抽象基类作为二进制接口 (接口与实现分离)

ifaststring.h

class IFastString {
public:
	virtual int Length(void) const = 0;
	virtual int Find(const char *psz) const = 0;
};

extern "C" IFastString* CreateFastString(const char* psz);

IFastString* CreateFastString(const char* psz)
{
    return new FastString(psz);
}

class FastString : public IFastString{
    const int m_len;
	char* m_psz;
public:
	FastString(const char *psz);
	~FastString();
	int Length(void) const;
	int Find(const char *psz)const;
};

调用:

int f()
{
    IFastString *pfs = CreateFastString("Liukang");
    int n = pfs->Find("kang");
    delete pfs;
    return n;
}

由于接口类的析构函数并不是虚函数,这意味着delete的调用并不会动态找到派生类的析构函数,导致内存泄漏

class IFastString {
public:
	virtual int Delete(void) const = 0; //add
	virtual int Length(void) const = 0;
	virtual int Find(const char *psz) const = 0;
};

在FastString实现类中增加Delete的实现,释放内存

int FastString::Delete(void)
{
    delete this;
}

调用:

int f()
{
    int n = -1;
    IFastString *pfs = CreateFastString("Deface me");
    if(pfs)
    {
        pfs->Find("ace me");
        pfs->Delete();
    }
    return n;
}

在FastString.dll中,除了一个入口函数CreateFastString外,其它所以入口函数都是虚函数。

1.7 运行时多态

到此其实我们已经进入C++库调用的显式加载了

IFastString *CallCreateFastString(const char* psz){
    static IFastString* (*pfn)(const char*) = 0;
    if(pfn){
        const TCHAR szDll[] = __TEXT("FastString.dll");
        const char szFn[] = "CreateFastString";
        HINSTANCE h = LoadLibrary(szDll);
        if(h)
            *(FARPROC*)&pfn = GetProcAddress(h, szFn);
    }
    return pfn ? pfn(psz) : 0;
}

这样的好处是,客户不需要连接dll的引入库lib,对dll没有依赖性。用到才装载dll,没用到就不会被装载。

1.8 对象的扩展性

假设我们需要扩展Load和Save的方法,可按下面的方式

class IExtensibleObject {
public:
	virtual void *Dynamic_Cast(const char* pszType) = 0;
	virtual void Delete(void) = 0;
};
class IFastString : public IExtensibleObject {
public:
	virtual int Length() = 0;
	virtual int Find(const char* psz) = 0;
};
class IPersistentObject : public IExtensibleObject {
public:
	virtual bool Load(const char* pszFileName) = 0;
	virtual bool Save(const char* pszFileName) = 0;
};

有了这样的类型层次之后,客户就可以利用编译器独立的结构,动态地查询对象是否实现了某个指定的接口

bool SaveString(IFastString* pfs, const char* pszFN)
{
	bool bResult = false;
	IPersistentObject *ppo = (IPersistentObject*)pfs->Dynamic_Cast("IPersistentObject");
	if (ppo)
	{
		bResult = ppo->Save(pszFN);
	}
	return bResult;
}

FastString中的Dynamic_Cast的实现可以简单使用显式的静态类型转换功能:

void* FastString::Dynamic_Cast(const char* pszType) {
	if (0 == strcmp(pszType, "IFastString"))
		return static_cast<IFastString*>(this);
	else if (0 == strcmp(pszType, "IPersistentObject"))
		return static_cast<IPersistentObject*>(this);
	else if (0 == strcmp(pszType, "IExtensibleObject")
		return static_cast<IFastString*>(this);
	return 0;
}
/*
注意,当客户请求公共的基接口IExtensibleObject 时,实现类将自己静态转换为IFastString, 是因为
return static_cast(this); 有二义性,因为IFastString和IPersistentObject都是从IExtensibleObject继承过来的。
如果IExtensibleObject是IFastString和IPersistentObject的虚基类,那么这个转换就不会有二义性的问题,所以这条语句能够正确编译。
然而,引入虚基类将导致在结果对象中加入不必要的运行时复杂性,而且也会带来编译器相信性。这是因为虚基类是另一项“编译器厂商可以选择自己的实现方法”的c++语言特性。
*/

1.9 资源管理

考虑下面的客户代码:

void f(void)
{
	IFastString* pfs = 0;
	IPersistentObject* ppo = 0;
	pfs = CreateFastString("Feed BOB");
	if (pfs) {
		ppo = (IPersistentObject*)pfs->Dynamic_Cast("IPersistentObject");
		if (!ppo) {
			pfs->Delete();
		}
		else {
			ppo->Save("C:\\autoexec.bat");
			ppo->Delete();
		}
	}
}

客户必须记录下哪个指针是与哪个对象联系在一起的,并且每个对象只能调用一次Delete方法。对于上面的简单代码,并不是繁重的负担,但如果在复杂的客户代码中,管理这些关系会变得非常复杂,并容易出错。为了简化,把管理对象生命周期的责任放在对象的实现部分。参考智能指针的实现方式。

在IExtensibleObject基类中增加两个接口

class IExtensibleObject {
public:
	virtual void *Dynamic_Cast(const char* pszType) = 0;
	virtual void DuplicatePointer(void) = 0;	//add
	virtual void DestroyPointer(void) = 0;		//add
};

在FastString类中增加下面的实现

class FastString : public IFastString ,public IPersistentObject
{
	int m_cPtrs;
public:
	FastString(const char *psz) :m_cPtrs(0) {}
	void DuplicatePointer(void) {
		m_cPtrs++;
	}
	void DestroyPointer(void) {
		if (--m_cPtrs == 0)
			delete this;
	}
};

在两个复制的地方增加指针的计数

//extern "C" 对外接口,new在堆中,复制到stack中
IFastString* CreateFastString(const char* psz)
{
	IFastString* pFsResult = new FastString(psz);
	if (pFsResult)
		pFsResult->DuplicatePointer();
	return pFsResult;
}
//有指针的复制
void* FastString::Dynamic_Cast(const char* pszType) {
	void* pvResult = 0;
	if (0 == strcmp(pszType, "IFastString"))
		pvResult = static_cast<IFastString*>(this);
	else if (0 == strcmp(pszType, "IPersistentObject"))
		pvResult = static_cast<IPersistentObject*>(this);
	else if (0 == strcmp(pszType, "IExtensibleObject")
		pvResult = static_cast<IFastString*>(this);
	//pvResult含有一个复制的指针,所以需要调用
	((IExtensibleObject*)pvResult).DuplicatePointer();
	return pvResult;
}

至此,客户的代码就可以写成:

void f(void)
{
	IFastString* pfs = 0;
	IPersistentObject* ppo = 0;
	pfs = CreateFastString("Feed BOB");
	if (pfs) {
		ppo = (IPersistentObject*)pfs->Dynamic_Cast("IPersistentObject");
		if (ppo) {
			ppo->Save("C:\\autoexec.bat");
			ppo->DestroyPointer();
		}
		pfs->DestroyPointer();
	}
}

1.10 我们走到哪了?

其实不知不觉,我们刚刚一步步设计了组件对象模型(COM, Component Object Model)

你可能感兴趣的:(C++)