技术(一):跟踪类的生命周期的类

自己跟踪自己的类
设计得当的类可以为理解程序的动态行为提供一个强有力的工具

  • Trace类怎样提供有关函数执行和类操作的调试信息

设计一个跟踪类

1
2
3
4
5
class Trace {
public:
Trace() { cout << "Hello\n"; }
~Trace() { cout << "Goodbye\n"; }
};
  • 使程序在明显改变自身行为的同时说明自己在做什么

  • 如函数Foo:

1
2
3
4
void foo {
Trace xxx;
// do something
}
  • 跟踪多个函数,无法确定哪个函数产生输出
1
2
3
4
5
6
7
8
9
10
11
12
class Trace {
public:
Trace(const char* p, const char* q): bye(q) {
cout << p;
}
~Trace() {
cout << bye;
}
private:
const char* bye;
};
  • 带两个参数的Trace构造函数
1
2
3
4
void foo() {
Trace xxx("begin foo\n", "end foo\n");
// do something
}
  • 3个容易发现的点

    1. 要打印的消息有很多共同点
    1. 使跟踪消息称为可选(禁止调试时可以关闭调试输出)会带来好处
    1. 可以在别的地方输出调试消息(而不只是cout)就不会打断程序的其他输出
  • 可以让构造函数记住被跟踪的函数对的名字,指定构造函数和析构函数打印begin和end消息以减少用户输入文本量

  • 输出:禁止输出/重定向输出

    • 可以添加一个全局指针指向调试消息输出流
  • 跟踪请求通常都是聚集在一起的

    • 逻辑上需要所有Trace对象同时将消息发送到给定文件
    • 应该创建一个类管理整组Trace对象的输出
  • Channel类包含一个指向输出流的指针,允许Trace类访问这个指针

1
2
3
4
5
6
7
8
class Channel {
friend class Trace;
ostream* trace_file;
public:
Channel(ostream* o = &cout): trace_file(o) { }
void reset(ostream* o) { trace_file = o; }
};
  • 在Trace类中使用这个类重定向输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Trace {
public:
Trace(const char* s, Channel* c): name(s), cp(c) {
if (cp->trace_file)
*cp->trace_file << "begin " << name << endl;
}
~Trace() {
if (cp->trace_file)
*cp->trace_file << "end " << name << endl;
}
private:
Channel* cp;
Const char* name;
};
  • 定义用于跟踪消息的逻辑分组
    • Channel subsystemX(&cout);
    • Channel subsystemY(0);
  • 与subsystemX相关的Trace对象将往cout进行打印

  • 与subsystemY相关的Trace对象不会产生输出
1
2
3
4
void foo() {
Trace xxx("foo", subsystemX);
// do something
}
  • 通过重新设置Channel可以关闭或重定向跟踪输出
    • subsystemX.reset(0); // 关闭跟踪
    • subsystemX.reset(&some_stream); // 重定向跟踪

创建死代码

调试类的潜在问题:即使关闭输出测试trace_file时进入和退出每个函数都耗费时间、空间

  • 如果C++实现足够聪明,可以不必重写用户代码而免除几乎所有的代码生成的开销

    • 编译器能够识别出Trace构造函数和析构函数的代码在某些环境中是死代码(引起额外开销的没用的代码)
    • 通过一个全局变量的测试,监视构造函数和析构函数;生成产品代码时将该全局变量的值设为常数零
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static const int debug = 0;
class Trace {
public:
Trace(const char* s, Channel* c) {
if (debug) {
name = s;
cp = c;
if (cp->trace_file)
*cp->trace_file << "begin " << name << endl;
}
}
~Trace() {
if (debug) {
if (cp->trace_file)
*cp->trace_file << "end " << name << endl;
}
}
private:
Channel* cp;
const char* name;
};
  • 显示赋值操作符初始化变量而不是在构造函数初始化器中
    • 保证debug为零时跳过初始化

生成对象的审计跟踪

  • 改成跟踪对象的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Obj_trace {
public:
Obj_trace(): ct(++count) {
cout << "Object " << ct << " constructed" << endl;
}
~Obj_trace() {
cout << "Object " << ct << " destroyed" << endl;
}
Obj_trace(const Obj_trace&): ct(++count) {
cout << "Object " << ct << " constructed" << endl;
}
Obj_trace& operator=(const Obj_trace&) {
return *this;
}
private:
static int count;
int ct;
};
int obj_trace::count = 0;
  • 每次创建一个Obj_trace对象时该对象都会获得一个由构造函数和析构函数打印的唯一序列号

  • 例:

1
2
3
4
class String {
// do the same thing
Obj_trace xxx;
};

验证容器行为

  • 检查每个被构造的对象是否都被销毁
  • 通过将Obj_trace作为容器中的对象
1
2
3
4
5
6
int main() {
Container<Obj_trace> x(3);
Container<Obj_trace> y = x;
x = y;
// ......
}

Summary

  • 这种类用来跟踪函数的进入和退出
    • 可以成为验证其他类运行状况的工具
    • 设计得当在绝大多数情况下可以直接放入产品代码中

《C++沉思录(Cplusplus Thinking)》笔记