Policy Based Design and MultiInheritance

星期四, 十二月 21, 2006

Policy Based Design and MultiInheritance

基于策略的设计(Policy Based Design)包含两个重要的部分:策略类(Policy-Classes)和具有极大张力的核心。
许多人在看基于策略的设计的时候,往往注意到了前者(策略类),注意到了正交分析的思想,却忽视了后者(核心)。这是因为策略类的思想较为容易接收,而且已经存在很多类似的概念。于是乎很多人认为基于策略的设计是作者在炒冷饭,是概念的炒作。还有人认为声称掌握了基于策略的设计思想,但是给出的代码却是基于单纯的多继承。
其实Andrei AlexandrescuModern C++ Design中已经明确区别了基于策略的设计和多继承之间的区别。使用多继承是无法实现基于策略的设计的!正如前面所说,基于策略的设计包含两个方面:策略类和核心。而多继承包含什么呢?也包括两个方面:多个基类和子类的实现。下面就分别对比一下。
策略类与多继承的基类。
这两者比较类似,区别不是很大。但是策略类在涵盖范围上面,可以说,是超越了“类”的。策略类在理论上面可以是类型(包括基本类型和自定义类型)、数据和模板。这是因为模板可以接收三种参数:数值模板参数、类型模板参数和模板模板参数。相比之下,可以用作多继承的基类的东东,只有“类”了。
核心与多继承中子类的实现。
这是两个概念的更加重大的区别,也是基于策略的设计较多继承更加优越的地方。优越的地方有二:基于策略的设计已经为用户提供了一个实现的框架;基于策略的设计可以避免滥用多继承导致的组合爆炸问题。
实现的框架就是指前面所说的具有极大张力的核心。例如我们使用Loki::SmartPtr的时候,从来都是拿来就用,从来都没有考虑过SmartPtr的内部实现问题。这就是因为SmartPtr已经为我们提供了一个实现框架,我们需要作的只是选择不同的策略类进行组合。几个零件,往SmartPtr上面一安就OK了。
如果换作多继承呢?多继承如何来处理这个问题?用户自己来实现?自己实现,费时费力,还免不了有一个bug,一点都没有体现出复用的优势,不利于软件质量的提高。
这位看官说了,让基础库的设计者为我们提供这个实现。好,还是举SmartPtr的例子,SmartPtr的定义如下:
1 template
2 <
3 typename T;
4 template <class> class OwnershipPolicy = RefCounted,
5 class ConversionPolicy = DisallowConversion,
6 template <class> class CheckingPolicy = AssertCheck,
7 template <class> class StoragePolicy = DefaultSPStorage
8 >
9 class SmartPtr;
考虑比较简单的情况,继承库的实现者们为每个Policy提供了两个个预定义的实现,那么四个Policy一组合,就意味着新产生了16个新的子类。呜乎哀哉,爆炸了。不但组合爆炸,而且还不解决问题:如果用户使用自定义的Policy,依然要自行派生新的子类。
而基于策略的设计,为用户提供了一个使用模板实现的核心,这个核心只需要一个实现,不需要用户重复实现;而且可以接收用户自定义的Policy,拿来就用,不需要多余的代码,达到了充分的代码复用。


Read more!

一个好用的重定向类

星期三, 十二月 06, 2006

一个好用的重定向类

想编写自己的IDE软件么?是不是需要一个重定向类呢?我在codeguru上面找到了一个好用的,另外加以了一些改进。


前一段时间要编写一个类似于IDE的软件,发现要通过GUI截获标准通道的输出还是比较麻烦的事情。网上有一些介绍的文章,但是发现都比较复杂,而且封装性不是很好。

在www.codeguru.com上面,我偶然发现了这个类,它编写的很好,可以截获stdout和stderr的输出,而且封装性好,使用方便。

美中不足的就是这个类里面指定的输出方式是输出到一个CEdit控件里面,这就极大的限制了改类的使用,我将其进行了改进:主要是提取一个输出的接口,如果用户希望输出到其他控件,只需要自己继承一个子类并实现,而不需要修改原有的代码。这也是对依赖导致原则(DIP)的一种应用吧。下面就把所有的代码贴出来。

// FILE :Redirect.h

#ifndef REDIRECT_H_INCLUDED__
#define REDIRECT_H_INCLUDED__
// the file is download from www.codeguru.com

class CRedirect
{
public:
class COutputer{
public:
COutputer(CWnd* pWnd):_pWnd(pWnd){}
virtual void Output(LPCTSTR text) = 0;
protected:
CWnd* _pWnd; // the window that output the text
};
public:

CRedirect
(
LPCTSTR szCommand,
COutputer& pOutput,
LPCTSTR szCurrentDirectory = NULL
);

~CRedirect();

// public member functions
virtual void Run();
virtual void Stop();

protected:
void AppendText(LPCTSTR Text);
void PeekAndPump();
void SetSleepInterval(DWORD dwMilliseconds);
void ShowLastError(LPCTSTR szText);

protected:
bool m_bStopped;
DWORD m_dwSleepMilliseconds;
COutputer& m_pOutput;
LPCTSTR m_szCommand;
LPCTSTR m_szCurrentDirectory;
};


class CEditOutputer : public CRedirect::COutputer
{
public:
CEditOutputer(CWnd* pWnd ):COutputer(pWnd){}
void Output(LPCTSTR text)
{
CEdit* pEdit = dynamic_cast(_pWnd);
if (pEdit) {
int Length = pEdit->GetWindowTextLength();

pEdit->SetSel(Length, Length);
pEdit->ReplaceSel(text);
pEdit->LineScroll( pEdit->GetLineCount() );
}
}
};

class CListBoxOutputer : public CRedirect::COutputer
{
public:
CListBoxOutputer(CWnd* pWnd):COutputer(pWnd){}
void Output(LPCTSTR text)
{
CListBox* pList = (CListBox*)_pWnd;
if (pList) {
const int bufLen = 100;
TCHAR buffer [bufLen];
while (*text) {
TCHAR* p = buffer;
int cCopied = 0;
while ( *text && *text!= _T('n') && cCopied < bufLen -1) {
*p ++ = * text++;
}
*p = _T('');
text ? text++ : text;
pList->AddString(buffer);
}
pList->SetCurSel(pList->GetCount() -1);
}
}
};
#endif // REDIRECT_H_INCLUDED__

// FILE :Redirect.cpp

//------------------------------------------------------------------------------
// Redirect.cpp : implementation file
//
// Creates a child process that runs a user-specified command and redirects its
// standard output and standard error to a CEdit control.
//
// Written by Matt Brunk (brunk@gorge.net)
// Copyright (C) 1999 Matt Brunk
// All rights reserved.
//
// This code may be used in compiled form in any way. This file may be
// distributed by any means providing it is not sold for profit without
// the written consent of the author, and providing that this notice and the
// author's name is included in the distribution. If the compiled form of the
// source code in this file is used in a commercial application, an e-mail to
// the author would be appreciated.
//
// Thanks to Dima Shamroni (dima@abirnet.co.il) for providing the essential
// code for this class.
//
// Thanks to Chris Maunder (chrismaunder@codeguru.com) for the PeekAndPump()
// function (from his CProgressWnd class).
//
// Initial Release Feb 8, 1999
//------------------------------------------------------------------------------

//////////////////////////////////////////////////////////////////////////
// the code is modified by Guo Congbin (guocongbin@sei.buaa.edu.cn)
// on Dec 23, 2005

#include "stdafx.h"
#include "Redirect.h"

const int BUF_SIZE = 10240;

CRedirect::CRedirect(LPCTSTR szCommand, COutputer& pOutput, LPCTSTR szCurrentDirectory)
:m_bStopped(false),m_dwSleepMilliseconds(100),m_pOutput(pOutput),m_szCommand(szCommand),
m_szCurrentDirectory(szCurrentDirectory)
{
}

CRedirect::~CRedirect()
{
}

void CRedirect::Run()
{
HANDLE PipeReadHandle;
HANDLE PipeWriteHandle;
PROCESS_INFORMATION ProcessInfo;
SECURITY_ATTRIBUTES SecurityAttributes;
STARTUPINFO StartupInfo;
BOOL Success;

//--------------------------------------------------------------------------
// Zero the structures.
//--------------------------------------------------------------------------
ZeroMemory( &StartupInfo, sizeof( StartupInfo ));
ZeroMemory( &ProcessInfo, sizeof( ProcessInfo ));
ZeroMemory( &SecurityAttributes, sizeof( SecurityAttributes ));

//--------------------------------------------------------------------------
// Create a pipe for the child's STDOUT.
//--------------------------------------------------------------------------
SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
SecurityAttributes.bInheritHandle = TRUE;
SecurityAttributes.lpSecurityDescriptor = NULL;

Success = CreatePipe
(
&PipeReadHandle, // address of variable for read handle
&PipeWriteHandle, // address of variable for write handle
&SecurityAttributes, // pointer to security attributes
0 // number of bytes reserved for pipe (use default size)
);

if ( !Success )
{
ShowLastError(_T("Error creating pipe"));
return;
}

//--------------------------------------------------------------------------
// Set up members of STARTUPINFO structure.
//--------------------------------------------------------------------------
StartupInfo.cb = sizeof(STARTUPINFO);
StartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
StartupInfo.wShowWindow = SW_HIDE;
StartupInfo.hStdOutput = PipeWriteHandle;
StartupInfo.hStdError = PipeWriteHandle;

//----------------------------------------------------------------------------
// Create the child process.
//----------------------------------------------------------------------------
Success = CreateProcess
(
NULL, // pointer to name of executable module
LPTSTR(m_szCommand), // command line
NULL, // pointer to process security attributes
NULL, // pointer to thread security attributes (use primary thread security attributes)
TRUE, // inherit handles
0, // creation flags
NULL, // pointer to new environment block (use parent's)
m_szCurrentDirectory, // pointer to current directory name
&StartupInfo, // pointer to STARTUPINFO
&ProcessInfo // pointer to PROCESS_INFORMATION
);

if ( !Success )
{
ShowLastError(_T("Error creating process"));
return;
}

DWORD BytesLeftThisMessage = 0;
DWORD NumBytesRead;
TCHAR PipeData[BUF_SIZE];
DWORD TotalBytesAvailable = 0;

for ( ; ; )
{
NumBytesRead = 0;

Success = PeekNamedPipe
(
PipeReadHandle, // handle to pipe to copy from
PipeData, // pointer to data buffer
1, // size, in bytes, of data buffer
&NumBytesRead, // pointer to number of bytes read
&TotalBytesAvailable, // pointer to total number of bytes available
&BytesLeftThisMessage // pointer to unread bytes in this message
);

if ( !Success )
{
ShowLastError(_T("PeekNamedPipe fialed"));
break;
}

if ( NumBytesRead )
{
Success = ReadFile
(
PipeReadHandle, // handle to pipe to copy from
PipeData, // address of buffer that receives data
BUF_SIZE - 1, // number of bytes to read
&NumBytesRead, // address of number of bytes read
NULL // address of structure for data for overlapped I/O
);

if ( !Success )
{
ShowLastError(_T("ReadFile fialed"));
break;
}

//------------------------------------------------------------------
// Zero-terminate the data.
//------------------------------------------------------------------
PipeData[NumBytesRead] = '';

//------------------------------------------------------------------
// Replace backspaces with spaces.
//------------------------------------------------------------------
for ( DWORD ii = 0; ii < NumBytesRead; ii++ )
{
if ( PipeData[ii] == _T('b') )
{
PipeData[ii] = ' ';
}
}

//------------------------------------------------------------------
// If we're running a batch file that contains a pause command,
// assume it is the last output from the batch file and remove it.
//------------------------------------------------------------------
TCHAR *ptr = _tcsstr(PipeData, _T("Press any key to continue . . ."));
if ( ptr )
{
*ptr = '';
}

//------------------------------------------------------------------
// Append the output to the CEdit control.
//------------------------------------------------------------------
AppendText(PipeData);

//------------------------------------------------------------------
// Peek and pump messages.
//------------------------------------------------------------------
PeekAndPump();
}
else
{
//------------------------------------------------------------------
// If the child process has completed, break out.
//------------------------------------------------------------------
if ( WaitForSingleObject(ProcessInfo.hProcess, 0) == WAIT_OBJECT_0 )
//lint !e1924 (warning about C-style cast)
{
break;
}

//------------------------------------------------------------------
// Peek and pump messages.
//------------------------------------------------------------------
PeekAndPump();

//------------------------------------------------------------------
// If the user cancelled the operation, terminate the process.
//------------------------------------------------------------------
if ( m_bStopped )
{
Success = TerminateProcess
(
ProcessInfo.hProcess,
0
);

if ( Success )
{
AppendText(_T("rnCancelled.rnrnProcess terminated successfully.rn"));
}
else
{
ShowLastError(_T("Error terminating process."));
}

break;
}

//------------------------------------------------------------------
// Sleep.
//------------------------------------------------------------------
Sleep(m_dwSleepMilliseconds);
}

}

//--------------------------------------------------------------------------
// Close handles.
//--------------------------------------------------------------------------
Success = CloseHandle(ProcessInfo.hThread);
if ( !Success )
{
ShowLastError(_T("Error closing thread handle."));
}

Success = CloseHandle(ProcessInfo.hProcess);
if ( !Success )
{
ShowLastError(_T("Error closing process handle."));
}

Success = CloseHandle(PipeReadHandle);
if ( !Success )
{
ShowLastError(_T("Error closing pipe read handle."));
}

Success = CloseHandle(PipeWriteHandle);
if ( !Success )
{
ShowLastError(_T("Error closing pipe write handle."));
}

}


void CRedirect::ShowLastError(LPCTSTR szText)
{
LPVOID lpMsgBuf;
DWORD Success;

//--------------------------------------------------------------------------
// Get the system error message.
//--------------------------------------------------------------------------
Success = FormatMessage
(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //lint !e1924 (warning about C-style cast)
LPTSTR(&lpMsgBuf),
0,
NULL
);

CString Msg;

Msg = szText;
Msg += _T("rn");
if ( Success )
{
Msg += LPTSTR(lpMsgBuf);
}
else
{
Msg += _T("No status because FormatMessage failed.rn");
}

AppendText(Msg);

}

void CRedirect::PeekAndPump()
{
MSG Msg;
while (::PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE))
{
(void)AfxGetApp()->PumpMessage(); //lint !e1924 (warning about C-style cast)
}
}

void CRedirect::Stop()
{
m_bStopped = true;
}

void CRedirect::AppendText(LPCTSTR Text)
{
m_pOutput.Output(Text);
}

void CRedirect::SetSleepInterval(DWORD dwMilliseconds)
{
m_dwSleepMilliseconds = dwMilliseconds;
}



Read more!

Common C++ 应用之三:分析本地xml文件

Common C++ 应用之三:分析本地xml文件

Common C++的demo中有一个parse xml文件的例子,但是这个例子在windows平台下,读取本地文件时候总是显示 xml文件格式错误,这是因为这个例子中使用的类是从URLStream类和XMLStream类派生出来的,因此不适合读取本地文件。我们可以从stl中的ifstream类和XMLStream类来编写一个分析本地xml文件的例子。具体的代码如下。



#include <cc++/common.h>
#include <iostream>
#include <cstdlib>
#include <fstream>

using namespace std;
using namespace ost;

class myXMLParser : public ifstream, public XMLStream
{
private:
bool out;
int read(unsigned char *buffer, size_t len)
{
ifstream::read((char *)buffer, len);
len = gcount();
return len;
}

void characters(const unsigned char *text, size_t len)
{
if(out){
cout <<text;
}
}
void startElement(const unsigned char *name, const unsigned char **attr)
{
if( strcmp((const char*)name, "CITY") == 0){
out = true;
}
else{
out = false;
}
}

void endElement(const unsigned char *name)
{
out =false;
}
public:
void Close(void)
{
ifstream::close();
}
myXMLParser(char* path): ifstream(path){
out =false;
}
};

int main(int argc, char **argv)
{
URLStream::Error status;

// url.setProxy("home.sys", 8000);
try
{
while(--argc)
{
++argv;
cout << "fetching " << *argv << endl;
myXMLParser xml(*argv);
/*
status = xml.get(*argv);
if(status)
{
cout << "failed; reason=" << status << endl;
xml.Close();
continue;
}
*/
cout << "Parsing..." << endl;
if(!xml.parse())
cout << "not well formed..." << endl;
xml.Close();
cout << ends;
}
}
catch(...)
{
cerr << "url " << *argv << " failed" << endl;
}
}


Read more!

门口的车站

星期二, 十二月 05, 2006

门口的车站

小区门口新开了一趟公交线路,这对于地处城乡结合部的我们小区本来是一件好事。而且新线路与老线路都经过公司,这样我上班本来是会更加的方便。但是如果好事不被好好办,那就让人觉得有些冒火。

新线路的车站与原有线路的车站相距足足有200米的距离,在一个车站等车就不可能搭上另外一趟车。因此,现在的两条线路和以前的一条现图,对我来说没有什么两样。

现在,我也有我的对策了。我出门后会先去距离家比较近的车站。如果没有来车,我就继续走,走到两个车站的中间,然后拼命的向公交车来的方向张望。看见哪趟车来了,我就赶紧向哪个车站狂奔。每次上车之后,我都是气喘吁吁,狼狈至极啊。

唉,中国人的公众服务意识,真是让人无言以对。


Read more!

CommonC++ 应用之二:使用Get访问HTTP服务器

星期一, 十二月 04, 2006

CommonC++ 应用之二:使用Get访问HTTP服务器

相对于POST,基于Common C++,使用GET方法访问http服务器就简单很多了。我们只需要构造一个url字符串,包括所有的参数(前面的"http://"不可以省略)。然后调用URLStream的get方法。下面的代码也是用来调用一个webservice来得到北京的当前天气的。代码和Common C++中的urlfetch.cpp几乎完全相同。




// Copyright (C) 2001 Open Source Telecom Corporation.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// As a special exception to the GNU General Public License, permission is
// granted for additional uses of the text contained in its release
// of Common C++.
//
// The exception is that, if you link the Common C++ library with other
// files to produce an executable, this does not by itself cause the
// resulting executable to be covered by the GNU General Public License.
// Your use of that executable is in no way restricted on account of
// linking the Common C++ library code into it.
//
// This exception does not however invalidate any other reasons why
// the executable file might be covered by the GNU General Public License.
//
// This exception applies only to the code released under the
// name Common C++. If you copy code from other releases into a copy of
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way. To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.

#include <cc++/common.h>
#include <iostream>
#include <cstdlib>

using namespace std;
using namespace ost;

class myURLStream : public URLStream
{
private:
void httpHeader(const char *header, const char *value)
{
cout << "HEADER " << header << "=" << value << endl;
}
};
int main(int argc, char **argv)
{
myURLStream url;
char cbuf[1024];
URLStream::Error status;
int len;

const char* addr = "http://www.webservicex.net/globalweather.asmx/GetWeather?CityName=beijing&CountryName=china";
try
{
status = url.get(addr);
if(status)
{
cout << "failed; reason=" << status << endl;
url.close();
}
else{
cout << "loading..." << endl;
while(!url.eof())
{
url.read(cbuf, sizeof(cbuf));
len = url.gcount();
if(len > 0)
cout.write(cbuf, len);
}
url.close();
cout << ends;
}
}
catch(...)
{
cerr << "url " << addr << " failed" << endl;
}
return 0;
}


Read more!

CommonC++应用之一:使用POST访问HTTP服务器

星期五, 十二月 01, 2006

CommonC++应用之一:使用POST访问HTTP服务器

因为要进行网络应用方面的编程,所以在网上找到了这个CommonC++的库。这是GNU的一个项目,我下载的版本是1.5.3
它里面提供的功能倒是很全面,体积也比较小巧,所以还是值得利用。不过里面的bug也不少,有些还是比较低级的bug。

我写了一个使用POST方法访问HTTP服务器的case,用来得到天气预报信息(服务就是前面的文章中提到的得到天气预报的web service服务)。
case的代码使用CommonC++自带的urlfetch.cpp该过来的

// myurlfeth.cpp
#include <cc++/common.h>
#include <iostream>
#include <cstdlib>

#ifdef CCXX_NAMESPACES
using namespace std;
using namespace ost;
#endif

class myURLStream : public URLStream
{
private:
void httpHeader(const char *header, const char *value)
{
cout << "HEADER " << header << "=" << value << endl;
}
};

int main(int argc, char **argv)
{
myURLStream url;
url.setProtocol(URLStream::protocolHttp1_1);

char cbuf[1024];
int len;
#ifdef CCXX_EXCEPTIONS
try
{
#endif
char* surl = "http://www.xview.com.cn/WebService/Weather.asmx/GetWeatherDataSet";
char* city="北京";
char *var = "cityName";
char buf[30];
urlEncode(city, buf, 30);
const char* para[3];
para[0] = (const char*)var;
para[1] = buf;
para[2] = NULL;
url.post(surl,para, 1);
cout << "loading..." << endl;
while(!url.eof())
{
url.read(cbuf, sizeof(cbuf));
len = url.gcount();
if(len > 0)
cout.write(cbuf, len);
}
url.close();
cout << ends;
#ifdef CCXX_EXCEPTIONS
}
catch(...)
{
cerr << "url fetch failed" << endl;
}
#endif
return 0;
}

不过要让这段代码工作,需要修改src/url.cpp,修正其中的两个bug
将src/url.cpp的第666行的switch...case结构修改如下

// src/url.cpp, line 666
switch(urlmethod)
{
case methodHttpPost:
// get the content-length of request.
// use '&' or '=' to connect different parameter,
// therefore the total length is the length of all vars plus count -1
while(*args)
{
var = *args;
bool alone = strchr(var, '=');
len += strlen(var);
count++;
++args;
}
len += count -1;
count = 0;
//len += 2; //guocb
str << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
str << "Content-Length: " << (unsigned)len << "\r\n";
break;
case methodHttpPostMultipart:
while(*args)
str << *(args++) << "\r\n";
default:
break;
}

Read more!

Get Weather by webservice, implemented in python

星期三, 十一月 29, 2006

Get Weather by webservice, implemented in python

##########################################################
#use the webservice provided by www.xview.com.cn
#get the weather forcast
import httplib, urllib

url='www.xview.com.cn'
para=url.urlencode({'cityName':'北京'})
headers={'Content-Type': 'application/x-www-form-urlencoded'}
conn=httplib.HTTPConnection(url)
conn.request('POST','/WebService/Weather.asmx/GetWeatherDataSet', para, headers)
resp=conn.getresponse()
res_xml=''
if resp.status != 200:
print 'ERROR'
else:
res_xml=resp.read()
conn.close()
print res_xml
#######################################################
#use the webservice proivded by www.webserivcex.net
#get the weahter of now (not weather forcast)
import httplib
url='www.webservicex.net'
conn=httplib.HTTPConnection(url)
conn.request('GET', '/globalweather.asmx/GetWeather?CityName=Beijing&CountryName=china')
resp=conn.getresponse()
if resp.status = 200:
print resp.read()
else:
print ‘ERROR’
conn.close()

Read more!

C++单元测试框架:CxxTest

星期六, 十一月 25, 2006

C++单元测试框架:CxxTest

单元测试流行了有一段时间了。虽然真正做单元测试的人未必有多少,但是至少单元测试的概念得到了普及。单元测试技术中离不开单元测试框架。一说单元测试框架,很多人都能想起Junit和CppUnit等xUnit框架。毕竟xUnit框架家族历史悠久。但是不得不指出,xUnit并不是完美的,尤其是在C++中,CppUnit还远远没有得到所有人的认可。在C++中,目前还有很多的单元测试框架,例如Boost.TestCxxTest、CppUnitLite、NanoUnit、Unit++等等。今天我就介绍一下其中非常优秀的代表CxxTest。

CxxTest的中文资料不多,我把它的用户手册大概翻译一下贴出来。最新的用户手册可以从http://cxxtest.sourceforge.net/guide.html下载。
这里只是其中支持MockObject一章,其余的以后再贴吧

1 伪对象(Mock Object)(版本3.10.0)Mock对象是一种有效的测试方法。它的主要思想是向被测试的代码传递一个特殊的对象。例如测试一个实现了某种基于TCP的协议类,可能需要一个抽象的ISocket接口的参数。在测试用例中,就可以传递一个MockSocket对象。这个对象可以做一些有利于测试的任务,例如记录一个“发送”的所有数据的日志,以便事后确认。虽然听起来不错,但是C/C++代码开发人员的问题是需要经常调用那些无法重载的全局函数。想一下那些使用fopen()、fwrite()和fclose()的代码。在测试这些代码的时候真正的创建文件并不是一个优雅的解决方法。而且更进一步,无法测试异常情况,比如fopen()失败的情况。虽然在某种情况下,我们可以在测试代码中引发这种异常情况发生,但是很快就会导致测试代码混乱与无法维护。CxxTest通过允许在测试代码中重载任何全局函数来解决该问题。下面是一个对其工作原理的介绍,然后是实例演示。
· 对于每个要重载的函数,首先使用宏CXXTEST_MOCK_GLOBAL处理一下(具体过程将在下面的实例中详细介绍)。
· 在测试代码中,不能直接调用全局函数,而是调用名字空间T(代表Test)中的相应函数。例如使用T::fopen()代替fopen()。这在思想上与使用抽象接口代替具体类相同。
· 与被测代码进行链接的是一个直接调用原始的fopen函数的源文件。
· 与测试代码进行链接的是一个通过调用mock object来实现T::fopen()的源文件。
· 在测试中需要创建一个从T::Base_fopen派生而来的类,并实现其fopen()函数。只需要在测试代码中创建一个该类的实例,测试代码中对T::fopen的调用就会自动重定向。这些步骤似乎有些过于繁琐,所以通过一个实例进行解释效果更好。下面我们就重载标准函数time()。
· 首先编写供测试代码和实际代码使用的测试头文件。
// T/time.h #include #include CXXTEST_MOCK_GLOBAL( time_t, /* Return type */ time, /* Name of the function */ ( time_t *t ), /* Prototype */ ( t ) /* Argument list */ );
· 在测试用例代码中,要使用上面的头文件代替原来的系统头文件,而且要使用T::time()代替原有的time()。
// code.cpp
#include

int generateRandomNumber()
{
return T::time( NULL ) * 3;
}
· 我们需要创建一个实现T::time()的源文件。该实现就是直接调用真正的time函数。不过要在该文件中包含头文件之前要定义宏CXXTEST_MOCK_REAL_SOURCE_FILE。
// real_time.cpp #define CXXTEST_MOCK_REAL_SOURCE_FILE #include
· 在开始测试之前,我们需要一个为测试专门实现的T::time()。实现过程和上面的一样简单:
// mock_time.cpp #define CXXTEST_MOCK_TEST_SOURCE_FILE #include
· 最有意思的地方开始了。在测试代码中只需要创建一个mock,测试代码就是自动的去调用之。
// TestRandom.h #include #include class TheTimeIsOne : public T::Base_time { public: time_t time( time_t * ) { return 1; } }; class TestRandom : public CxxTest::TestSuite { public: void test_Random() { TheTimeIsOne t; TS_ASSERT_EQUALS( generateRandomNumber(), 3 ); } };
4.5.1 实际使用似乎这些工作看起来比较繁琐,但是一旦开始使用这种方法,你就不会再这么认为。最艰巨的部分是将此方法与你系统中的构建环境协同工作。因此我写了一个与此类似的简单实例。该实例使用了GNU Make和G++,在sample/mock下。
4.5.2 关于Mock函数的进一步讨论
4.5.2.1 Void函数void函数略有不同,需要使用CXXTEST_MOCK_VOID_GLOBAL宏重载。该宏除了没有指定返回值,其他与CXXTEST_MOCK_GLOBAL完全相同。参见sample/mock/T/stdlib.h。
4.5.2.2 在测试代码中调用真正的函数在测试代码中偶尔也需要调用真正的函数。方法是创建一个特殊的mock对象,名字类似于T::Real_time。在测试代码中构造一个该类的对象会将T::time()重定向到真正的time函数。
4.5.2.3当无法修改真正函数实现时有时在测试代码中需要调用一些无法修改实现的函数。例如在用户态下运行的测试代码中要调用一个操作系统的内核函数。使用CxxTest的mock框架可以为被测代码创建一个可以测试的实现,同时无需修改被测代码中对真实函数的调用。实现方法就是使用宏CXXTEST_SUPPLY_VOID_GLOBAL(以及CXXTEST_SUPPLY_VOID_GLOBAL)。例如下面代码为Win32内核函数IoCallDirver创建了mock:
CXXTEST_SUPPLY_GLOBAL( NTSTATUS, /* Return type */ IoCallDriver, /* Name */ ( PDEVICE_OBJECT Device, /* Prototype */ PIRP Irp ), ( Device, Irp ) /* How to call */ );现在你的测试代码就可以正常的调用IoCallDriver()(不需要名字空间前缀T::),而且测试代码可以将T::Base_IoCallDriver作为普通的mock对象来使用。【注】由于这些宏同时声明了函数原型(因此上面的例子中就无需再包含真正的),它们都有一个使用extern "C"版本:CXXTEST_SUPPLY_GLOBAL_C和CXXTEST_SUPPLY_GLOBAL_VOID_C。
4.5.2.4 名字空间中的函数有时候,需要被重载的函数不想time函数那样是属于全局空间,它们可能是其他名字空间的全局函数甚至是某个类中的静态函数。默认的mock实现无法处理这种情况,需要使用更加一般化的CXXTEST_MOCK。下面用例子来解释一下这个宏。假设名字空间Files中,需要重载其中的函数
bool Files::FileExists(const String &name)因此mock类的名字是T::Base_Files_FileExists,需要实现的函数是fileExists。定义的方式如下(使用真实函数的名字作为mock类和类中要实现的函数的名字):
CXXTEST_MOCK( Files_FileExists, /* Suffix of mock class */ bool, /* Return type */ fileExists, /* Name of mock member */ ( const String &name ), /* Prototype */ Files::FileExists, /* Name of real function */ ( name ) /* Parameter list */ );无需多言,相应的处理void函数的宏是CXXTEST_MOCK_VOID。对于CXXTEST_SUPPLY_GLOBAL也有一个对应的宏。使用下面的Win32 DDK的例子可予以解释。
CXXTEST_SUPPLY( AllocateIrp, /* => T::Base_AllocateIrp */ PIRP, /* Return type */ allocateIrp, /* Name of mock member */ ( CCHAR StackSize ), /* Prototype */ IoAllocateIrp, /* Name of real function */ ( StackSize ) /* Parameter list */ );同时由此宏可以派生出来CXXTEST_SUPPLY_VOID、CXXTEST_SUPPLY_C与CXXTEST_SUPPLY_VOID_C。
4.5.2.5 重载函数如果有多个具有相同名字的全局函数,自然不能创建多个同名的mock类。解决方法是使用上面提及的更具有一般性的宏CXXTEST_MOCK、CXXTEST_MOCK_VOID,只是要给两个mock类起不同的名字。
4.5.2.6 改变mock的名字空间最后,如果你不喜欢或者不能使用名字T作为mock函数的前缀,你可以通过定义CXXTEST_MOCK_NAMESPACE来改变它。


Read more!

在Word里面使程序代码语法高亮的宏

在Word里面使程序代码语法高亮的宏

关键字 语法高亮 word VBA

keywordsyntax highlight, word, macro, VBA

最近我经常在word 里面写东西,发现程序代码拷贝到word 里面就没有了在代码编辑器里面的那种语法高亮的效果,感觉不爽。于是我上网搜了搜,发现目前在word 中实现语法高亮的方法主要是通过安装一个插件。由于我先天的对插件比较反感,所以自己动手,使用wordoffice 软件都支持的VBA (Visual BAsic For Application) 写了一个语法高亮的宏。

这个宏的功能比较简单,就是利用得到文档中选中部分的代码,然后分词,判断该词的类别,然后着色。我现在使用的分词方法是VBA 提供的,大部分情况下和我们预期的比较一致。但是在某些情况下,比如连续的分隔符,这种分词方法会和C 语言分析器的分词结果不同的。

这个宏除了可以语法着色,还可以为代码标注行号。(听说侯捷在《word 排版艺术》一书中也有一个为代码添加行号的宏。不知道他的宏和我的宏是否雷同。如有雷同,纯属巧合:)

' script to high light code In document

Private Function isKeyword(w) As Boolean

    Dim keys As New Collection

    With keys

        .Add " if": .Add "else": .Add "switch": .Add "cAse": .Add "default": .Add "break"

        .Add "goto": .Add "return": .Add "for": .Add "while": .Add "do": .Add "contInue"

        .Add "typedef": .Add "sizeof": .Add "NULL": .Add "new": .Add "delete": .Add "throw"

        .Add "try": .Add "catch": .Add "namespace": .Add "operator": .Add "this": .Add "const_cAst"

        .Add "static_cast": .Add "dynamic_cast": .Add "reInterpret_cAst": .Add "true"

        .Add "false": .Add "null": .Add "usIng": .Add "typeid": .Add "and": .Add "and_eq"

        .Add "bitand": .Add "bitor": .Add "compl": .Add "not": .Add "not_eq": .Add "or"

        .Add "or_eq": .Add "xor": .Add "xor_eq"

    End With

    isKeyword = isSpecial(w, keys)

End Function

 

Private Function isSpecial(ByVal w As String, ByRef col As Collection) As Boolean

    For Each i In col

        If w = i Then

            isSpecial = True

            Exit Function

        End If

    Next

    isspeical = False

End Function

 

Private Function isOperator(w) As Boolean

    Dim ops As New Collection

    With ops

        .Add "+": .Add "-": .Add "*": .Add "/": .Add "&": .Add "^": .Add ";"

        .Add "%": .Add "#": .Add "!": .Add ":": .Add ",": .Add "."

        .Add "||": .Add "&&": .Add "|": .Add "=": .Add "++": .Add "--"

        .Add "\'": .Add "\"""

    End With

    isOperator = isSpecial(w, ops)

End Function

 

Private Function isType(ByVal w As String) As Boolean

    Dim types As New Collection

    With types

        .Add "void": .Add "struct": .Add "union": .Add "enum": .Add "char": .Add "short": .Add "Int"

        .Add "long": .Add "double": .Add "float": .Add "signed": .Add "unsigned": .Add "const": .Add "static"

        .Add "extern": .Add "auto": .Add "register": .Add "volatile": .Add "bool": .Add "clAss": .Add " private"

        .Add "protected": .Add "public": .Add "fri end": .Add "InlIne": .Add "template": .Add "virtual"

        .Add "Asm": .Add "explicit": .Add "typename"

End With

 isType = isSpecial(w, types)

End Function

Sub SyntaxHighlight()

    Dim wordCount As Integer

    Dim d As Integer

    ' set the style of selection

    Selection.Style = "code"

   

    d = 0

    wordCount = Selection.Words.Count

    Selection.StartOf wdWord

    While d < wordCount

        d = d + Selection.MoveRight (wdWord, 1, wdExtend)

        w = Selection.Text

        If isKeyword(Trim(w)) = True Then

            Selection.Font.Color = wdColorBlue

        ElseIf isType(Trim(w)) = True Then

            Selection.Font.Color = wdColorDarkRed

            Selection.Font.Bold = True

        ElseIf isOperator(Trim(w)) = True Then

            Selection.Font.Color = wdColorBrown

        ElseIf Trim(w) = "//" Then

            'lIne comment

            Selection.Move End wdLIne, 1

            commentWords = Selection.Words.Count

            d = d + commentWords

            Selection.Font.Color = wdColorGreen

            Selection.MoveStart wdWord, commentWords

         ElseIf Trim(w) = "/*" Then

            'block comment

           

            While Selection.Characters.LAst <> "/"

                Selection.MoveLeft wdCharacter, 1, wdExtEnd

                Selection.Move EndUntil ("*")

                Selection.MoveRight wdCharacter, 2, wdExtEnd

            Wend

 

            commentWords = Selection.Words.Count

            d = d + commentWords

            Selection.Font.Color = wdColorGreen

            Selection.MoveStart wdWord, commentWords

        End If

        'move the start of selection to next word

        Selection.MoveStart wdWord

    Wend

   

    ' prepare For set lIne number

    Selection.MoveLeft wdWord, wordCount, wdExtend

    SetLIneNumber

End Sub

 

Private Sub SetLIneNumber()

    Dim lines As Integer

    lines = Selection.Paragraphs.Count

    Selection.StartOf wdParagraph

    For l = 1 To lines

        lIneNum = l & " "

        If l < 10 Then

            lIneNum = lIneNum & " "

        End If

        Selection.Text = lIneNum

        Selection.Font.Bold = False

        Selection.Font.Color = wdColorAutomatic

        p = Selection.MoveDown (wdLIne, 1, wdMove)

        Selection.StartOf wdLIne

    Next l

End Sub

 

 


Read more!

一个模板的实际应用

一个模板的实际应用

 

关键词: C++ 模板 克隆 clone

 

多态和虚函数大大增强了 C++语言的表达能力,但是有时候单纯依赖多态并不能很好的解决问题。

例如有下面的一个问题。一个类继承体系,基类为 Shape,子类有RectCircle等。现在要为每个子类增加一个用来表示类 ID的成员,即需要给每个子类增加一个静态成员(当然不能在基类里面添加静态成员了,因为那样所有的子类都会共享改成员。必须要在每个子类中添加一个静态成员)。

1  class Rect: public Shape{

2  static int clsid;

3  };

4  int Rect::clsid = 0;

5  class Circle: public Shape{

6  static int clsid;

7  };

8  int Circle::clsid = 1;

 

为了能够使用统一的接口来得到这个 id的值,需要在基类中增加一个接口:

1  class Shape{

2     const int GetClassID() const  = 0;

3  };

 

每个子类中也必须来实现这个接口:

1  const int Rect::GetClassID() const {

2     return clsid;

3  }

4  const int Circle::GetClassID() const{

5     return clsid;

6  }

代码虽然都差不多,但是却无法进行抽象。如果子类众多,在每个子类中都要实现这样的一部分代码,实在不是一种好味道。

 

与此问题类似的另外一个问题就是 Clone。基类中定义了一个clone 的接口,每个子类都要实现。虽然子类的clone方法非常类似,都是 new一个新实例,但是因为要 new不同的实例,所以也无法抽象到基类中实现。

 

要解决这个 id的问题,我们可以进行这样的思考。

 

重新定义一个基类,里面包含一个静态的成员变量,让每个 Shape的子类同时多继承这个类。但是问题依然,所有的子类都引用同一个静态成员。为此我们可以将这个类变为一个模板类,模板参数就是子类的类型。如下:

1  template<typename T>struct ClsID{

2  static int clsid;

3  };

4  class Rect: public Shape, public ClsID<Rect>{

5    ...

6  };

7  class Circle: public Shape, public ClsID<Circle>{

8    ...

9  };

 

那么如何访问这些成员呢?通过基类 Shape我们依然无法访问他们。我们可以依然在Shape 中定义一个GetClassID 函数,然后依然在每个子类中实现。但是这样我们似乎回到老路上。但是无论如何,这个GetClassID 的接口是必须能够通过Shape的指针来访问的。

 

我们将这个函数再次提取出来,放到一个单独的接口里面,然后让 Shape从这个接口继承。

 

1  struct ClsIDBase{

2     virtual const int GetClassID() const = 0 ;

3  };

4  class Shape: public ClsIDBase{

5    ...

6  };

 

而在上面出现的 ClsID类里面,我们来实现这个接口:

1  template<typename T>struct ClsID : public virtual ClsIDBase{

2     static int clsid;

3     const int GetClassID() const{

4       return clsid;

5    }

6  };

7  template<typename T> int ClsID<T>::clsid ;

8  class Rect: public Shape, public ClsID<Rect>{

9    ...

10 };

11 class Circle : public Shape, public ClsID<Circle>{

12   ...

13 };

 

这样一来,我们就无需在每个子类中手工机械的重复那些相似的代码了。

 

对于那个 clone的问题,可以完全使用这个思路来解决。

 


Read more!