??xml version="1.0" encoding="utf-8" standalone="yes"?>
?994q? Greg Colvin向C++标准委员会提Z自己设计的智能指针:auto_ptr和counted_ptr。auto_ptr实现基本的RAII理Q不可复Ӟcounted_ptr采用引用计数实现了一个可复制的智能指针。两者用于不同的场合?br> 但是标准委员会最l只通过了auto_ptrQƈ且对auto_ptr加入了一个古怪的“所有权转移”语义。后来auto_ptr和counted_ptrq入了Boost C++ 库,改名为scoped_ptr和shared_ptr?br>
std::auto_ptr只所以设计ؓ可拷贝的Q也许是Z以下考虑Q比如下例函敎ͼ
f1中的参数所指向的对象应该由谁来删除呢?调用者还是被调用者?如果不看E序文档的话Q无法知道这一炏Vf2函数也存在同L问题?br>
用auto_ptr可以消除q种歧义性:
管如此Qauto_ptr?#8220;所有权转移”语义q是会带来副作用Q因Z修改原值的帔R拯q背了一般的设计原则Q它也许会在你意想不到的情况下就把对象{UM。它也不能用于标准容器中?br> 所以auto_ptr在新的标准库已经不再推荐使用。取而代之的是unique_ptr。unique_ptr与auto_ptrcMQ但限制了auto_ptr的拷贝行为。同Ӟ像上面D的例子一Punique_ptr可以作ؓ函数的参数和q回g用。这是因为C++增加了一个新的特征:叛_引用?br>
shared_ptr也进入了标准库。对于引用计数的指针而言Q@环引用是一个大问题。标准库为此把shared_ptr定义为强引用指针Q它q实C一个弱引用指针weak_ptr。显Ӟ标准库ƈ没有从根本上解决循环引用的问题,它把q个问题交给了程序员。在一个简单的pȝ中,你可以区分用shared_ptr和weak_ptrQ以此来避免出现循环引用。但是在一个大的对象系l中Q有时还是容易出错。@环引用的问题Q严重减׃shared_ptr的可用性?br>
那么能不能自动检是否出现@环引用呢Q事实上Q对于shared_ptrq种使用非R入式{略实现的智能指针,是很隑֮现自动检的。但是如果采用R入式设计Q我们可以引入一些接口,来解册个问题。@环引用的,实际上是图论中的回\问题?br>
本文?a style="FONT-SIZE: 12pt" title="eXile" href="http://www.shnenglu.com/eXile/">eXile 原创Q{载请表明原脓地址?nbsp;http://www.shnenglu.com/eXile/?/p>
Google C++ Testing Framework Primer
译Q?a target="_blank">Ray Li (ray.leex@gmail.com)
修改日期Q?008q???br>原文参见Q?a >http://code.google.com/p/googletest/wiki/GoogleTestPrimer
译文地址Qhttp://www.javaeye.com/topic/212024
IntroductionQؓ什么需?/strong>Google C++ 试框架Q?/strong>
Google C++ 试框架帮助你更好地~写C++试?
无论你是在LinuxQWindowsQ还是Mac环境下工作,只要你编写C++代码Q?span class="hilite1">Google 试框架都可以帮上忙?
那么Q哪些因素才能构成一个好的测试?以及Q?span class="hilite1">Google C++ 试框架怎样满q些因素Q我们相信:
因ؓGoogle C++ 试框架Z著名的xUnit架构Q如果你之前使用qJUnit或PyUnit的话Q你会感觉非常熟悉。如果你没有接触q这些测试框Ӟ它也只会占用你大U?0分钟的时间来学习基本概念和上手。所以,让我们开始吧Q?
NoteQ本文偶会?#8220;Google Test”来代?#8220;Google C++ 试框架”?
基本概念
使用Google TestӞ你是从编?em>断言开始的Q而断a是一些检查条件是否ؓ真的语句。一个断a的结果可能是成功、非致命p|Q或者致命失败。如果一个致命失败出玎ͼ他会l束当前的函敎ͼ否则Q程序l正常运行?
试使用断言来验证被代码的行ؓ。如果一个测试崩溃或是出C个失败的断言Q那么,该测?em>p|Q否则该试成功?
一个测试案例(test caseQ包含了一个或多个试。你应该自q试分别归类到测试案例中Q以反映被测代码的结构。当试案例中的多个试需要共享通用对象和子E序Ӟ你可以把他们攑ֈ一个测试固Ӟtest fixtureQ类中?
一?em>试E序可以包含多个试案例?
从编写单个的断言开始,到创建测试和试案例Q我们将会介l怎样~写一个测试程序?
断言
Google Test中的断言是一些与函数调用怼的宏。要试一个类或函敎ͼ我们需要对其行为做出断a。当一个断ap|ӞGoogle Test会在屏幕上输代码所在的源文件及其所在的位置行号Q以及错误信息。也可以在编写断aӞ提供一个自定义的错误信息,q个信息在失败时会被附加?span class="hilite1">Google Test的错误信息之后?
断言常常成对出现Q它们都试同一个类或者函敎ͼ但对当前功能有着不同的效果。ASSERT_*版本的断ap|时会产生致命p|Qƈl束当前函数。EXPECT_*版本的断a产生非致命失败,而不会中止当前函数。通常更推荐用EXPECT_*断言Q因为它们运行一个测试中可以有不止一个的错误被报告出来。但如果在编写断a如果p|Q就没有必要l箋往下执行的试Ӟ你应该用ASSERT_*断言?
因ؓp|的ASSERT_*断言会立M当前的函数返回,可能会蟩q其后的一些的清洁代码Q这样也怼DI间泄漏。根据泄漏本w的特质Q这U情? 也许值得修复Q也可能不值得我们兛_——所以,如果你得到断a错误的同Ӟq得C一个堆查的错误Q记住上面我们所说的q一炏V?
要提供一个自定义的错误消息,只需要?lt;<操作W,或一?lt;<操作W的序列Q将其输入到框架定义的宏中。下面是一个例子:
M能够被输出到ostream中的信息都可以被输出C个断a宏中——特别是C字符串和string对象。如果一个宽字符? Qwchar_t*Qwindows上UNICODE模式TCHAR*或std::wstringQ被输出C个断a中,在打印时它会被{换成UTF-8 ~码?
基本断言
下面q些断言实现了基本的true/false条g试?
致命断言 | 非致命断a | 验证条g |
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition为真 |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition 为假 |
CQ当它们p|ӞASSERT_*产生一个致命失败ƈ从当前函数返回,而EXCEPT_*产生一个非致命p|Q允许函数l运行。在两种情况下,一个断ap|都意味着它所包含的测试失败?
有效q_QLinux、Windows、Mac?
二进制比?/strong>
本节描述了比较两个值的一些断a?
致命断言 | 非致命断a | 验证条g |
ASSERT_EQ(expected, actual); | EXPECT_EQ(expected, actual); | expected == actual |
ASSERT_NE(val1, val2); | EXPECT_NE(val1, val2); | val1 != val2 |
ASSERT_LT(val1, val2); | EXPECT_LT(val1, val2); | val1 < val2 |
ASSERT_LE(val1, val2); | EXPECT_LE(val1, val2); | val1 <= val2 |
ASSERT_GT(val1, val2); | EXPECT_GT(val1, val2); | val1 > val2 |
ASSERT_GE(val1, val2); | EXPECT_GE(val1, val2); | val1 >= val2 |
在出现失败事件时Q?span class="hilite1">Google Test会将两个|Val1?em>Val2Q都打印出来。在ASSERT_EQ*和EXCEPT_EQ*断言Q以及我们随后介l类似的断言Q中Q你应该把你希望试的表辑ּ攑֜actualQ实际|的位|上Q将其期望值放?em>expectedQ期望|的位|上Q因?span class="hilite1">Google Test的测试消息ؓq种惯例做了一些优化?
参数值必L可通过断言的比较操作符q行比较的,否则你会得到一个编译错误。参数D必须支持<<操作W来D入到ostream中。所有的C++内置cd都支持这一炏V?
q些断言可以用于用户自定义的型别Q但你必重载相应的比较操作W(?=?lt;{)。如果定义有相应的操作符Q推荐用ASSERT_*()宏,因ؓ它们不仅会输出比较的l果Q还会输Z个比较对象?
参数表达式L只被解析一ơ。因此,参数表达式有一定的副作用(side effectQ这里应该是指编译器不同Q操作符解析序的不定性)也是可以接受的。但是,同其他普通C/C++函数一P参数表达式的解析序是不定的(如,一U编译器可以自由选择一U顺序来q行解析Q,而你的代码不应该依赖于某U特定的参数解析序?
ASSERT_EQ()Ҏ针进行的是指针比较。即Q如果被用在两个C字符串上Q它会比较它们是否指向同L内存地址Q而不是它们所指向的字W串是否有相同倹{所以,如果你想对两个C字符Ԍ例如Qconst char*Q进行值比较,请用ASSERT_STREQ()宏,该宏会在后面介绍到。特别需要一提的是,要验证一个C字符串是否ؓI(NULLQ,使用ASSERT_STREQ(NULL, c_string)。但是要比较两个string对象Ӟ你应该用ASSERT_EQ?
本节中介l的宏都可以处理H字W串对象和宽字符串对象(string和wstringQ?
有效q_QLinux、Windows、Mac?
字符串比?/strong>
该组断言用于比较两个C字符丌Ӏ如果你惌比较两个string对象Q相应地使用EXPECT_EQ、EXPECT_NE{断a?
致命断言 | 非致命断a | 验证条g |
ASSERT_STREQ(expected_str, actual_str); | EXPECT_STREQ(expected_str, actual_str); | 两个C字符串有相同的内?/td> |
ASSERT_STRNE(str1, str2); | EXPECT_STRNE(str1, str2); | 两个C字符串有不同的内?/td> |
ASSERT_STRCASEEQ(expected_str, actual_str); | EXPECT_STRCASEEQ(expected_str, actual_str); | 两个C字符串有相同的内容,忽略大小?/td> |
ASSERT_STRCASENE(str1, str2); | EXPECT_STRCASENE(str1, str2); | 两个C字符串有不同的内容,忽略大小?/td> |
注意断言名称中出现的“CASE”意味着大小写被忽略了?
*STREQ*?STRNE*也接受宽字符Ԍwchar_t*Q。如果两个宽字符串比较失败,它们的g做ؓUTF-8H字W串被输出?
一个NULLI指针和一个空字符串会被认为是不一?/em>的?
有效q_QLinux、Windows、Mac?
参见Q更多的字符串比较的技巧(如子字符丌Ӏ前~和正则表辑ּ匚wQ,请参见[Advanced Guide Advanced Google Test Guide]?
单的试
要创Z个测试:
TESTQ)的参数是从概括到Ҏ的?em>W一?/em>参数是测试案例的名称Q?em>W二?/em>参数是测试案例中的测试的名称。记住,一个测试案例可以包含Q意数量的独立试。一个测试的全称包括了包含它的测试案例名Uͼ及其独立的名U。不同测试案例中的独立测试可以有相同的名U?
举例来说Q让我们看一个简单的整数函数Q?
作者Matthias EttrichQ译者Googol LeeQ原文地址?a >q里?
在奇(TrolltechQ,Z改进Qt的开发体验,我们做了大量的研I。这文章里Q我打算分n一些我们的发现Q以及一些我们在设计Qt4时用到的原则Qƈ且展C如何把q些原则应用C的代码里?
设计应用E序接口QAPIQ是很难的。这是一门和设计语言同样隄艺术。这里可以选择太多的原则,甚至有很多原则和其他原则有矛盾?
现在Q计机U学教育把很大的力气攑֜法和数据结构上Q而很关注设计语a和框架背后的原则。这让应用程序员完全没有准备去面对越来越重要的Q务:创造可重用的组件?
在面向对象语a普及之前Q可重用的通用代码大部分是由库提供者写的,而不是应用程序员。在Qt的世界里Q这U状冉|了明昄改善。在M时候,用Qt~程是写新的组件。一个典型的Qt应用E序臛_都会有几个在E序中反复用的自定义组件。一般来_同样的组件会成ؓ其他应用E序的一部分。KDEQK桌面环境Q走得更q,用许多追加的库来扩展QtQ实C数百个附加类。(一般来_一个类是一个可重用lgQ原文这里没有写清楚。)
但是Q一个好的,高效的C++ API是由什么组成的呢?是好q是坏,取决于很多因素——比如,手头的工作和特定的目标群体。好的API有很多特性,一些特性是大家都想要的Q而另一些则是针对特定问题域的?
API是面向程序员的,用来描述提供l最l用LGUI是什么样子。API中的P带表E序员(ProgrammerQ,而不是程序(ProgramQ,用来API是给E序员用的,lhcȝE序员用的?
我们坚信API应该是最化且完整的Q拥有清C单的语义Q直觉化Q容易记忆,q且引导人写出易ȝ代码?
最后,CQ不同类型的用户会用到API的不同部分。虽然简单的实例化一个QtcL非常直觉化的Q让资深专家在试囑֭cd之前M遍文档,是很合理的?
q是个常见的误解Q更好的APIQ用更少的代码完成一件事。永q记住代码一ơ写,之后需要不断的阅读q理解。比如:
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
q比下面那样难读Q甚至难写)Q?
QSlider *slider = new QSlider(Qt::Vertical); slider->setRange(12, 18); slider->setPageStep(3); slider->setValue(13); slider->setObjectName("volume");
布尔参数通常会导致不易读的代码。更q一步,l一个已l存在的函数加入一个布参敎ͼq常常是个错误。在Qt里,一个传l的例子是repaint()Q这个函数带有一个布参敎ͼ来标识是否擦除背景(默认擦除Q。这让代码通常写成Q?
widget->repaint(false);
初学者很Ҏ把这句话理解?#8220;别重?#8221;Q?
q样做是考虑到布参数可以减一个函敎ͼ避免代码膨胀。事实上Q这反而增加了代码量。有多少Qt用户真的C了下面三行程序都是做什么的Q?
widget->repaint(); widget->repaint(true); widget->repaint(false);
一个好一些的API可能看v来是q样Q?
widget->repaint(); widget->repaintWithoutErasing();
在Qt4里,我们重新设计了widgetQ得用户不再需要不重画背景的重画widgetQ来解决q个问题。Qt4原生支持双缓存,废掉了这个特性?
q里q有一些例子:
widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true); textEdit->insert("Where's Waldo?", true, true, false); QRegExp rx("moc_*.c??", false, true);
一个显而易见的解决Ҏ是,使用枚Dcd代替布尔参数。这正是我们在Qt4?a >QString大小写敏感时的处理方法。比较:
str.replace("%USER%", user, false); // Qt 3 str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
怼的类应该含有怼的API。在必要的时候——就是说Q需要用运行时多态的时候——这可以通过l承实现。但是多态依旧会发生在设计时期。比如,如果你用QListBox代替QComboBoxQ或者用QSlider代替QSpinBoxQ你会发现相似的API使这U替换非常容易。这是我们所说的“静态多?#8221;?
静态多态也使API和程序模式更Ҏ记忆。作为结论,一l相关类使用怼的APIQ有时要比给每个cL供完的单独APIQ要好?
Q译注:C++ 0x要引入的conceptQ就是静态多态的语法层实现。这个要比单独的函数名相似更强大且易用。)
命名Q大概是设计API时唯一最重要的问题了。该怎么U呼q个c?成员函数该叫什么?
一些规则通常Ҏ有名字都是有用的。首先,像我之前提到的Q别用羃写。甚臛_明显的羃写,比如“prev”表示“previous”从长q看也是不划的Q因为用户必记住哪些词是羃写?
如果API本n不一_事情自然会变得很p糕Q比如, Qt3有activatePreviousWindow()和fetchPrev()。坚?#8220;没有~写”的规则更Ҏ创徏一致的API?
另一个重要但更加微妙的规则是Q在设计cȝ时候,必须力保证子类命名I间的干净。在Qt3里,没有很好的遵守这个规则。比如,?a >QToolButton来D例。如果你在Qt3里,对一?a >QToolButton调用name()、caption()、text()或者textLabel()Q你希望做什么呢Q你可以在Qt Designer里拿QToolButton试试Q?
׃对可L的xQname在Qt4里被UCobjectNameQcaption变成了windowsTitleQ而在QToolButton里不再有单独的textLabel属性?
标识一l类而不是单独给每个cL个恰当的名字。比如,Qt4里所有模式感知项目的视图c(model-aware item view classesQ都拥有-View的后~Q?a >QListView?a >QTableView?a >QTreeViewQ,q且对应Z目的类都用后缀-Widget代替Q?a >QListWidget?a >QTableWidget?a >QTreeWidgetQ?
当声明枚举时Q时刻记住,在C++Q不像Java和C#Q中Q用枚丑րg需要类型信息。下面的例子演示了给枚DDv个太q常用的名字所引v的危宻I
namespace Qt { enum Corner { TopLeft, BottomRight, ... }; enum CaseSensitivity { Insensitive, Sensitive }; ... }; tabWidget->setCornerWidget(widget, Qt::TopLeft); str.indexOf("$(QTDIR)", Qt::Insensitive);
在最后一行,Insensitive是什么意思?一个用于命名枚丑ր的指导思想是,在每个枚丑ր里Q至重复一个枚丄型名中的元素Q?
namespace Qt { enum Corner { TopLeftCorner, BottomRightCorner, ... }; enum CaseSensitivity { CaseInsensitive, CaseSensitive }; ... }; tabWidget->setCornerWidget(widget, Qt::TopLeftCorner); str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
当枚丑ր可以用“?#8221;q接h当作一个标志时Q传l的做法是将“?#8221;的结果作Z个int保存Q这不是cd安全的。Qt4提供了一个模板类 QFlags<T>来实现类型安全,其中T是个枚Dcd。ؓ了方便用,Qt为很多标志类名提供了typedefQ所以你可以使用cd Qt::Alignment代替QFlags<Qt::AlignmentFlag>?
Z方便Q我们给枚Dcd单数的名字(q样表示枚Dgơ只能有一个标志)Q?#8220;标志”则用复数名字。比如:
enum RectangleEdge { LeftEdge, RightEdge, ... }; typedef QFlags<RectangleEdge> RectangleEdges;
有些情况下,“标志“cM用了单数的名字。这Ӟ枚DcM?Flag做后~Q?
enum AlignmentFlag { AlignLeft, AlignTop, ... }; typedef QFlags<AlignmentFlag> Alignment;
Q这里ؓ啥不是把”标志“cȝ-Flag做后~Q而是把枚丑ր做后缀呢?感觉有点h……Q?
l函数命名的一个规则是Q名字要明确体现个函数是否有副作用。在Qt3Q常数函?a >QString::simplifyWhiteSpace()q反了这个原则,因ؓ它返回类一?a >QString实例Q而不是像名字所提示的那P更改了调用这个函数的实例本n。在Qt4Q这个函数被重命名ؓQString::simplified()?
参数名是E序员的重要信息来源Q虽然在使用APIӞq不直接展示在代码里。由于现代IDE在程序员写代码时可以自动昄参数名(是自动感知或者自动补全之cȝ功能Q,值得花时间给头文仉声明的参C个合适的名字Qƈ且在文档中也使用相同的名字?
l布属性的讄函数和提取函C个合适的名字QL非常痛苦的。提取函数应该叫做checked()q是isChecked()QscrollBarsEnabled()q是areScrollBarEnabled()?
在Qt4里,我们使用下列规则命名提取函数Q?
讄函数名字l承自提取函数名Q只是移掉了所有前~Qƈ使用set-做前~Q比如:setDown()q有setScrollBarsEnabled()。属性的名字与提取函数相同,只是L了前~?
传出参数的最佳选择是什么,指针q是引用Q?
void getHsv(int *h, int *s, int *v) const void getHsv(int &h, int &s, int &v) const
大部分C++书推荐在能用引用的地方就用引用,q是因ؓ一般认为引用比指针?#8220;安全且好?#8221;。然而,在奇(TrolltechQ,我们們使用指针Q因让代码更易读。比较:
color.getHsv(&h, &s, &v); color.getHsv(h, s, v);
只有W一行能清楚的说明,在函数调用后Qh、s和v有很大几率被改动?
Z展示如何实际应用q些概念Q我们将学习Qt3中的API QProgressBarq和Qt4里相通的API做比较。在Qt3里:
class QProgressBar : public QWidget { ... public: int totalSteps() const; int progress() const; const QString &progressString() const; bool percentageVisible() const; void setPercentageVisible(bool); void setCenterIndicator(bool on); bool centerIndicator() const; void setIndicatorFollowsStyle(bool); bool indicatorFollowsStyle() const; public slots: void reset(); virtual void setTotalSteps(int totalSteps); virtual void setProgress(int progress); void setProgress(int progress, int totalSteps); protected: virtual bool setIndicator(QString &progressStr, int progress, int totalSteps); ... };
API相当复杂Q且不统一。比如,仅从名字reset()q不能理解其作用QsetTotalSteps()和setProgress()是紧耦合的?
改进API的关键,是注意到QProgressBar和Qt4?a >QAbstractSpinBoxcd其子c?a >QSpinBoxQ?a >QSlider?a >QDial很相伹{解x法?用minimum、maximum和value代替progress和totalSteps。加入alueChanged()信号。加入setRange()函数?
之后观察progressString、percentage和indicator实际都指一个东西:在进度条上显C的文字。一般来说文字是癑ֈ比信息,但是也可以用setIndicator()设ؓL字符。下面是新的APIQ?
virtual QString text() const; void setTextVisible(bool visible); bool isTextVisible() const;
默认的文字信息是癑ֈ比信息。文字信息可以藉由重新实现text()而改变?
在Qt3 API中,setCenterIndicator()和setIndicatorFollowStyle()是两个媄响对齐的函数。他们可以方便的׃个函数实玎ͼsetAlignment()Q?
void setAlignment(Qt::Alignment alignment);
如果E序员不调用setAlignment()Q对齐方式基于当前的风格。对于基于Motif的风|文字居中显C;对其他风|文字靠在右辏V?
q是改进后的QProgressBar APIQ?
class QProgressBar : public QWidget { ... public: void setMinimum(int minimum); int minimum() const; void setMaximum(int maximum); int maximum() const; void setRange(int minimum, int maximum); int value() const; virtual QString text() const; void setTextVisible(bool visible); bool isTextVisible() const; Qt::Alignment alignment() const; void setAlignment(Qt::Alignment alignment); public slots: void reset(); void setValue(int value); signals: void valueChanged(int value); ... };
API需要质量保证。第一个修订版不可能是正确的;你必d试。写些用例:看看那些使用了这些API的代码,q证代码是否易诅R?
其他的技巧包括让别的人分别在有文档和没有文档的情况下Q用这些APIQ或者ؓAPIcd文档Q包括类的概q和独立的函敎ͼ?
当你卡住Ӟ写文档也是一U获得好名字的方法:仅仅是尝试把条目Q类Q函敎ͼ枚D|{等呢个Q写下来q且使用你写的第一句话作ؓ灉|。如果你不能扑ֈ一个精的名字Q这常常说明q个条目不应该存在。如果所有前面的事情都失败了q且你确认这个概늚存在Q发明一个新名字。毕竟,“widget”?“event”?#8220;focus”?#8220;buddy”q些名字是q么来的?