防御性编程
我们常用断言来达到这个目的。工作完成后,我们用NDEBUG来去掉断言。如下所示:
#ifdef NDEBUG
#define assert(cond) ((void)0)
#else
void assertImpl(const char*, const char*, long);
#define assert(cond) \
((cond) ? (void)0 : assertImpl(???))
#endif
Thinking in C++前几章笔记(一)
http://blog.163.com/zhoumhan_0351/blog/static/39954227201032124942513
如想开启或关闭程序中某些位置的断言,不但必须#define或#undef NDEBUG,而且还要重新包含<cassert>。
前置条件是使用该函数的必要条件,后置条件是该函数提供的结果,通过返回值或通过(by) side-effect.
The preconditions in derived classes must impose no extra requirements
beyond those in the base contract, and the postconditions must deliver at least
as much.
1、测试程序+编码比直接编码更快。最好用布尔表达式的集合来表示测试问题。
如下是一个例子
#ifndef DATE1_H
#define DATE1_H
#include <string>
class Date {
public:
// A struct to hold elapsed time:
Date();
Date(int year, int month, int day):years(year),months(month),days(day){}
int getYear() const {
return years;
}
int getMonth() const{
return months;
}
int getDay() const{
return days;
}
private:
int years;
int months;
int days;
};
#endif // DATE1_H ///:~
//: C02:SimpleDateTest.cpp
#include <iostream>
using namespace std;
// Test machinery
int nPass = 0, nFail = 0;
void test(bool t) { if(t) nPass++; else nFail++; }
int main() {
Date mybday(1951, 10, 1);
test(mybday.getYear() == 1951);
test(mybday.getMonth() == 10);
test(mybday.getDay() == 1);
cout << "Passed: " << nPass << ", Failed: "
<< nFail << endl;
}
2、TestSuite框架
如cppunit(http://sourceforge.net/apps/mediawiki/cppunit/index.php?title=Main_Page)
类名为 TestSuite。两个主要的类,test,suite.test是一个抽象基类,可以从这个类中派生用户自己的测试对象,只要重写run就行了。
http://junit.sourceforge.net/junit3.8.1/javadoc/junit/framework/TestSuite.html
讲述如何使用这个套件。
//: C02:Date1.h
// A first pass at Date.h.
#ifndef DATETEST_H
#define DATETEST_H
#include "../TestSuite/Test.h"
class DateTest : public TestSuite::Test {
Date mybday;
Date today;
Date myevebday;
public:
DateTest(): mybday(1951, 10, 1), myevebday("19510930") {}
void run() {
testOps();
testFunctions();
testDuration();
}
void testOps() {
test_(mybday < today);
...
}
void testFunctions() {
test_(mybday.getYear() == 1951);
...
}
void testDuration() {
Date d2(2003, 7, 4);
Date::Duration dur = duration(mybday, d2);
test_(dur.years == 51);
...
}
};
#endif // DATETEST_H ///:~
Running the test is a simple matter of instantiating a DateTest object and calling its run( ) member
function:
//: C02:DateTest.cpp
// Automated testing (with a framework).
//{L} Date ../TestSuite/Test
#include <iostream>
#include "DateTest.h"
using namespace std;
int main() {
DateTest test;
test.run();
return test.report();
}
Test类使用运行时类型识别(RTTI)来取得测试类的类名。默认将结果输出到屏幕上。可以用成员函数setStream()将结果输出到文件中。此外,还要fail_(),succeed_()函数等。
3、测试技术
(1)跟踪宏
定义如下类似的跟踪宏来进行:
#define TRACE(ARG) cout << #ARG << endl; ARG
#define D(a) cout << #a "=[" << a << "]" << endl;
(2)跟踪文件
定义如下格式
#include <iostream>
#include <fstream>
#ifndef TRACE_H
#define TRACE_H
#include <fstream>
#define TRACEON
#ifdef TRACEON
std::ofstream TRACEFILE__("C:\\TRACE.OUT");
#define cout TRACEFILE__
#endif //ifdef
#endif // TRACE_H ///:~
using namespace std;
int main() {
ifstream f("C:\\bsmain_runtime.log");
cout << f.rdbuf(); // Dumps file contents to file
} ///:~
如上所示,我们重定义了cout,通过定义一个文件,而进行跟踪。注意要在iostream后定义,不然会被其覆盖,达不到想要的效果了。当然这样定义是不符合C++标准的。
4、发现内存泄露
我们通过对new和delete分配次数进行计数,看其是否匹配,从而达到这个目的。重写new和delete。并且运用了宏FILE_和LINE_来存储文件名和行号。
//: C03:Tracetst.cpp {-bor}
#include <iostream>
#include <vector>
#include <cstring>
#ifndef MEMCHECK_H
#define MEMCHECK_H
#include <cstddef> // For size_t
// Usurp the new operator (both scalar and array versions)
void* operator new(std::size_t, const char*, long);
void* operator new[](std::size_t, const char*, long);
#define new new (__FILE__, __LINE__)
extern bool traceFlag;
#define TRACE_ON() traceFlag = true
#define TRACE_OFF() traceFlag = false
extern bool activeFlag;
#define MEM_ON() activeFlag = true
#define MEM_OFF() activeFlag = false
#endif // MEMCHECK_H ///:~
//: C02:MemCheck.cpp {O}
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <cstddef>
using namespace std;
#undef new
// Global flags set by macros in MemCheck.h
bool traceFlag = true;
bool activeFlag = false;
namespace {
// Memory map entry type
struct Info {
void* ptr;
const char* file;
long line;
};
// Memory map data
const size_t MAXPTRS = 10000u;
Info memMap[MAXPTRS];
size_t nptrs = 0;
// Searches the map for an address
int findPtr(void* p) {
for(size_t i = 0; i < nptrs; ++i)
if(memMap[i].ptr == p)
return i;
return -1;
}
void delPtr(void* p) {
int pos = findPtr(p);
// Remove pointer from map
for(size_t i = pos; i < nptrs-1; ++i)
memMap[i] = memMap[i+1];
--nptrs;
}
// Dummy type for static destructor
struct Sentinel {
~Sentinel() {
if(nptrs > 0) {
printf("Leaked memory at:\n");
for(size_t i = 0; i < nptrs; ++i)
printf("\t%p (file: %s, line %ld)\n",
memMap[i].ptr, memMap[i].file, memMap[i].line);
}
else
printf("No user memory leaks!\n");
}
};
// Static dummy object
Sentinel s;
} // End anonymous namespace
// Overload scalar new
void*
operator new(size_t siz, const char* file, long line) {
void* p = malloc(siz);
if(activeFlag) {
if(nptrs == MAXPTRS) {
printf("memory map too small (increase MAXPTRS)\n");
exit(1);
}
memMap[nptrs].ptr = p;
memMap[nptrs].file = file;
memMap[nptrs].line = line;
++nptrs;
}
if(traceFlag) {
printf("Allocated %u bytes at address %p ", siz, p);
printf("(file: %s, line: %ld)\n", file, line);
}
return p;
}
// Overload array new
void*
operator new[](size_t siz, const char* file, long line) {
return operator new(siz, file, line);
}
// Override scalar delete
void operator delete(void* p) {
if(findPtr(p) >= 0) {
free(p);
delPtr(p);
if(traceFlag)
printf("Deleted memory at address %p\n", p);
}
else if(!p && activeFlag)
printf("Attempt to delete unknown pointer: %p\n", p);
}
// Override array delete
void operator delete[](void* p) {
operator delete(p);
} ///:~
//: C02:MemTest.cpp
//{L} MemCheck
// Test of MemCheck system.
using namespace std;
class Foo {
char* s;
public:
Foo(const char*s ) {
this->s = new char[strlen(s) + 1];
strcpy(this->s, s);
}
~Foo() { delete [] s; }
};
int main() {
MEM_ON();
cout << "hello" << endl;
int* p = new int;
delete p;
int* q = new int[3];
delete [] q;
int* r;
delete r;
vector<int> v;
v.push_back(1);
Foo s("goodbye");
MEM_OFF();
} ///:~
说明:程序在不同的编译器运行结果不一定相同,只是体现一种思想,即我们可以通过重载new和delete,new[],delete[],增加一些辅助的数据来,统计分配和释放是否一致。
断言只能用于不变量条件的检查,而不能将其用于运行时错误处理。