显示标签为“lang-cpp”的博文。显示所有博文
显示标签为“lang-cpp”的博文。显示所有博文

2008年10月14日星期二

[TIC++] C13. Dynamic Object Creation

代码阅读<Thinking In C++>
Chapter 13. Dynamic Object Creation

一、在堆区创建内存

// c13: malloc_class.cpp
// malloc with class objects
#include <cstdlib>
#include <cstring>
#include <cstring>
#include <iostream>
using namespace std;

class obj {
int i, j, k;
enum { sz = 100 };
char buf[sz];
public:
void initialization() { // can't use constructors
cout << "initialization obj" << endl;
i = j = k = 0;
memset(buf, 0, sz);
}
void destroy() {
cout << "destroy obj" << endl;
}
};

int main()
{
obj *o = (obj*)malloc(sizeof(obj));
if (o == 0)
return -1;
o->initialization();
//sometimes later...
o->destroy();
free(o);
}
/* result:
initialization obj
destroy obj
*/

当C++对象创建的时候,将会发生两件事情:
1) 为空间分配内存
2) 调用构造方法来初始化这块内存

而空间创建问题(1),可以有以下选择:
1) 在程序开始前创建静态空间,位于静态内存区,拥有文件生命期
2) 在执行点处创建堆栈空间,他将在执行跳出方法的时候释放。堆栈的处理是处理器完成
的,自然非常高效
3) 在执行点处创建堆空间(heap),这被成为动态内存分配(!)。方法必须在运行调用的时候
创建堆空间,这意味着你可以决定任何时候创建,当然也要为释放负全权责任————生命期完
全由程序员掌控。

上面的例子展示了C处理堆区空间的方法:
这里的obj o对象的创建,没有使用到构造方法!这是非常糟糕的情况,因为你有可能选择
是否初始化,甚至可能忘记,初始化的丢失极为可能带来bug。

于是C++引入了一种新方法:

// c13: new_and_delete.cpp
#include <iostream>
using namespace std;

class tree {
int height;
public:
tree(int tree_height): height(tree_height) {}
~tree() { cout << "*"; }
friend ostream& operator <<(ostream &os, const tree* t) {
return os << "tree height is: "
<< t->height << std::endl;
}
};

int main(void)
{
tree *t = new tree(40);
cout << t;
delete t;
}
/* result
tree height is: 40
*/

new和delete保证了构造方法的正常调用,甚至还会检查内存申请是否调用成功。
这一切显得像堆栈内存分配一样简单。

二、delete void*

// c13: bad_void_pointer_deletion.cpp
#include <iostream>
using namespace std;

class object {
void *data;
const int size;
const char id;
public:
object(int sz, char c): size(sz), id(c) {
data = new char[size];
cout << "constructing object" << id
<< ", size = " << size << endl;
}
~object() {
cout << "desctructing object " << id << endl;
delete []data; // OK, just release storage;
// no desctructor calls are necessary;
}
};

int main() {
object *a = new object(40, 'a');
delete a;
void *b = new object(40, 'b');
delete b;
}
/* result:
constructing objecta, size = 40
desctructing object a
constructing objectb, size = 40
*/

object是一个包含 void *data的一个类,在这个类的析构方法中,我们看到使用了delete
来析构data,这没什么问题,因为这里要作的只是释放内存。

但是在main()中,对于delete来说,对象类型信息就非常必要了。由于delete a知道a是一
个class object,所以他使用到了~object()析构方法,data也被释放了。可是void *b就遇
到麻烦,他无法得知void*的类型信息,就无法调用析构方法,因此data将会永远的丢失,
这是一个静静的内存泄露。

如果你在C++工程中有内存泄露问题,那就要好好检查你的delete了。

三、介绍new handler

// c13: new_handler.cpp
#include <iostream>
#include <cstdlib>
#include <new>
using namespace std;

int count = 0;

void out_of_memory()
{
cerr << "memory exhausted after " << count << " allocations" << endl;
exit(1);
}

int main()
{
set_new_handler(out_of_memory);
while(1) {
count++;
new int[1000];
}
}

当new无法找到一个足够大的堆空间来存放对象,会出现何种情况呢?
这样的情况下,就会调用new-handler。默认的new-handler会抛出异常,当然可以定义新的
new-handler,打印出信息告诉你发生了什么。

这里就是用<new>的set_new_handler设置了方法,他会打印在经历多少次存储之后耗尽内存。

_

[TIC++] C11. References & the Copy-Constructor

代码阅读<Thinking In C++>
Chapter 11. References & the Copy-Constructor

一、再次介绍引用

// reference.cpp
int *f(int *x)
{
(*x)++;
return x;
}

int &g(int &x)
{
x++;
return x;
}

int &h()
{
int q;
//! return q; // error
static int x;
return x;
}

int main()
{
int a = 0;
f(&a);
g(a);
}

reference可以被简单视作一个常量指针,而这个指针会在预编译期间自动被解释掉。
ref最常用到的地方,就是作为参数和返回值使用了。对参数而言,任何形参的改变会作用
到实参;针对返回值,要注意的是返回的东西只是引用,必须保证要返回的内存在返回地可
见。
* 因此,你不能在h()中返回局部变量。

在介绍copy constructor之前,让我们来看常引用情况 ————

// const_ref.cpp
void f(int&) {}
void g(const int&) {}

int main() {
//! f(1); //invalid initialization of non-const reference
g(1);
}

当传递1这个实参的时候,编译器会为1分配内存,然后创造一个引用(int&)绑定到这个内存
地址。如果1没有被声明为const,那将是毫无意义的。f(1)将得到一个编译时错误。

最后是复合化的引用,指向指针的引用 ————

// c11: ref_to_pointer.cpp
#include <iostream>
using namespace std;

void increment(int* &i) { i++; }

int main()
{
int *i = 0;

cout << "i = " << i << endl;
increment(i);
cout << "i = " << i << endl;
}

这里阵阵改变的是指针(int*)i,而不是指针指向的值,但是这里传递的i。
在C中要做到这点,那就要使用二级指针了。

二、copy-constructor

// c11: howmany.cpp
#include <fstream>
#include <string>

using namespace std;
ofstream out("howmany.out");

class howmany {
static int object_cnt;
public:
howmany() { object_cnt++; }
static void print(const string& msg = "") {
if (msg.size() != 0)
out << msg << ": ";
out << "object_cnt = " << object_cnt << endl;
}
~howmany() {
object_cnt--;
print("~howmany()");
}
};

int howmany::object_cnt = 0;

// pass and return by value:
howmany f(howmany x)
{
x.print("x argument inside f()");
return x;
}

int main()
{
howmany h;
howmany::print("after construction of h");
howmany h2 = f(h);
howmany::print("after call to f()");
}
/* result:
after construction of h: object_cnt = 1
x argument inside f(): object_cnt = 1
~howmany(): object_cnt = 0
after call to f(): object_cnt = 0
~howmany(): object_cnt = -1
~howmany(): object_cnt = -2
*/

首先我们构建howmany h,调用构造函数,会得到object_cnt = 1;
然后我们使用f()来构建第二个对象h2 = f(h),编译器传递h拷贝,并没有使用构造函数来
创建h2。但是意想不到的是,离开f()的时候发生了析构,导致object_cnt = 0。
而最后的两个析构,使得object_cnt得到负数。

这是为什么呢?
因为在f()内,函数使用了C形式的bitcopy,我们得到的只是一份h的拷贝,他没有调用构造
方法,所以我们没有看到f()中objecgt_cnt = 2。而在后来,C++特性去保证了最后一步析
构,因此出现了object_cnt不升反降的结果。

C++中为了保证构造的完整性,因此提出了copy-constructor————

// c11: howmany.cpp
#include <fstream>
#include <string>
using namespace std;

ofstream out("howmany2.out");

class howmany2 {
string name;
static int object_cnt;
public:
howmany2(const string &id = ""): name(id) {
++object_cnt;
print("howmany2()");
}
~howmany2() {
--object_cnt;
print("~howmany()");
}
howmany2(const howmany2 &h): name(h.name) {
name += " copy";
++object_cnt;
print("howmany2(const howmany2&)");
}
void print(const string& msg = "") const {
if (msg.size() != 0)
out << msg << endl;
out << '\t' << name << ": "
<< "object_cnt = " << object_cnt << endl;
}
};

int howmany2::object_cnt = 0;

// pass and return by value:
howmany2 f(howmany2 x)
{
x.print("x argument inside f()");
return x;
}

int main()
{
howmany2 h("h");
out << "entering f()" << endl;
howmany2 h2 = f(h);
h2.print("h2 after call to f()");
out << "call f(), no return value" << endl;
f(h);
out << "after call to f()" << endl;
}
/*
howmany2()
h: object_cnt = 1
entering f()
howmany2(const howmany2&)
h copy: object_cnt = 2
x argument inside f()
h copy: object_cnt = 2
howmany2(const howmany2&)
h copy copy: object_cnt = 3
~howmany()
h copy: object_cnt = 2
h2 after call to f()
h copy copy: object_cnt = 2
call f(), no return value
howmany2(const howmany2&)
h copy: object_cnt = 3
x argument inside f()
h copy: object_cnt = 3
howmany2(const howmany2&)
h copy copy: object_cnt = 4
~howmany()
h copy copy: object_cnt = 3
~howmany()
h copy: object_cnt = 2
after call to f()
~howmany()
h copy copy: object_cnt = 1
~howmany()
h: object_cnt = 0
*/

我们可以看到,f()传递参数的时候,编译器将会把拷贝传值的过程交给copy-constructor,
同样return返回值,也需要这样的拷贝。
entering f()
howmany2(const howmany2&)
h copy: object_cnt = 2
x argument inside f()
h copy: object_cnt = 2
howmany2(const howmany2&)
h copy copy: object_cnt = 3

而后离开f(),这需要析构刚才创建的临时变量,保留return的值。

第二次f()没有返回值,因此情况稍微发生改变。此种调用忽略了返回值,编译器也会聪明
的直接在调用点析构此临时对象。

三、选择copy-construction
我们来看如何迫使系统自建拷贝构造方法

// c11: default_cc.cpp
// automatic creation of the copy-constructor
#include <iostream>
#include <string>
using namespace std;

class withcc {
public:
withcc() {}
withcc(const withcc &) {
cout << "withcc(withcc &)" << endl;
}
};

class wocc {
string id;
public:
wocc(const string &ident = ""): id(ident) {}
void print(const string &msg = "") const {
if (msg.size() != 0)
cout << msg << ": ";
cout << id << endl;
}
};

class composite {
withcc tc;
wocc oc;
public:
composite(): oc("composite()") {}
void print(const string &msg = "") const {
oc.print(msg);
}
};

int main() {
composite c;
c.print("content of c");
cout << "calling composite copy-constructor" << endl;
composite c2 = c;
c2.print("content of c2");
}
/* reuslt
content of c: composite()
calling composite copy-constructor
withcc(withcc &)
content of c2: composite()
*/

我们在这里看到两个案例:
1) class withcc拥有构造方法和拷贝构造方法。一旦包含拷贝构造函数,这就是在告诉编
译器:永远不要再自作主张,自动创建默认构造函数。因此,如果class withcc没有定义
withcc(),编译器会报错——composite中的tc没有办法创建。

2) class composite拥有构造方法,而没有拷贝构造方法。一旦需要拷贝构造方法,编译器
会为所有的成员对象调用他们的拷贝构造方法。(这通过组合或者继承可以实现composite)
在这里,c2 = c,就强迫调用了withcc和wocc的拷贝构造方法。而wocc没有拷贝构造方法,
因此系统为其自动定义一个,完成bitcopy。

接下来,通过小技巧来避免赋值拷贝

// c10: no_cc.cpp
class nocc {
int i;
nocc(const nocc &);
public:
nocc(int ii = 0): i(ii) {}
};

void f(nocc);

int main(void)
{
nocc n;
// copy-constructor is private!
//! f(n);
//! nocc n2 = n;
//! nocc n3(n);
}

通过将拷贝构造方法设置为私有,来避免赋值拷贝,这是一个非常简单的方法。
在此不用赘述。
_

2008年10月11日星期六

[TIC++] C10. Name Control

代码阅读<Thinking In C++>
Chapter 10. Name Control

一、局部变量作为static

// c10: static_objects_inside_funcs.cpp
#include <iostream>
using namespace std;

class X {
int i;
public:
X(int ii = 0):i(ii) {}
~X() { cout << "X::~X()" << endl; }
};

void f()
{
static X x1(47);
static X x2;
}

int main()
{
f();
} ///:~
/* result:
X::~X()
X::~X()
*/

当局部变量声明为static的时候,此变量将不会放置在堆栈区内,而是放置在程序的静态内
存区。这就意味着static变量:
* 具有全局生命期
* 初始化只进行一次
* 此变量在声明函数外部不可见

来解析一下静态对象的析构方法:

// c10: static_destructors.cpp
// static object destructors

#include <fstream>
using namespace std;

ofstream out("statdest.out");

class obj {
char c;
public:
obj(char cc): c(cc) {
out << "obj::obj() for " << c << endl;
}
~obj() {
out << "obj::~obj() for " << c << endl;
}
};

obj a('a');

void f()
{
static obj b('b');
}

void g()
{
static obj c('c');
}

int main()
{
out << "inside main()" << endl;
f();
//g() not called
out << "leaving main()" << endl;
}
/* statdest.out:
obj::obj() for a
inside main()
obj::obj() for b
leaving main()
obj::~obj() for b
obj::~obj() for a
*/

静态变量具有全局生命期,这样静态对象会在main()函数退出时候,或者调用C标准函数
exit()的时候被自动调用析构方法。这意味着:如果你在静态对象的析构方法内exit,就会
造成一个死循环这是很危险的。另外,调用abort()函数结束程序,将不会自动调用静态
对象的析构方法。

这个例子解释了一些基本概况,a是全局对象,b是f()内部的静态对象,他们的构造和析构
顺序正好相反:
cons a -> cons b -> des b --> des a
(在C++中,全局静态对象的构造函数是在main()之前被调用的)

二、全局变量为static
local_extern.cpp -- 第一个模块(main)

// c10: local_extern.cpp

#include <iostream>

int main()
{
extern int i;
std::cout << i << endl;;
}
/* result:
5
*/

local_extern2.cpp -- 第二个模块

// c10: local_extern2.cpp
int i = 5;

static 和 extern在 C++中是一对反义词。
* 默认全局变量是extern的,extern关键词标明次变量包含外部链接,可以在其他模块中引
用。而static则约束此变量为内部链接,不可外部引用。这对于“非成员”函数也同样适用。
file scope:
1) 全局变量声明默认为extern,而声明为static只会更改可视范围,这样变量只包含内部
链接————它仍旧会位于静态数据区域,无论static还是extern。

2) 局部变量声明默认为auto,若声明为static则会更改可视范围,并且更改存储区域(从
堆栈区更改到静态数据区);若声明为extern,则显示此变量位于别的模块。

3) 方法定义为static/extern,只会改变可视范围。

三、名字空间 namespaces

// c10: continuation.cpp
namespace mylib {
extern int x;
void f();
}

namespace mylib {
extern int y;
void g();
}

int main() {}

* 名字空间的定义必须在全局区内,或者被嵌套在其他名字空间内
* 定义的末尾没有";"
* 可以在多个头文件里添加名字空间定义(原本看起来像重复定义)
* 名字空间可以被引用为其他名字,比如
namespace bob = bobs_super_duper_library;
* 对名字空间内部数据的引用类似于成员操作
* 你不能使用名字空间建立实例,他根本不是一个类型

四、using 指令
namespace_int.h -- 定义名字空间Int

// c10: namespace_int.h
#ifndef __NAMESPACEINT_H
#define __NAMESPACEINT_H

namespace Int {
enum sign { positive, negative };
class Integer {
int i;
sign s;
public:
Integer(int ii = 0): i(ii),
s(i >= 0 ? positive : negative)
{}
sign get_sign() const { return s; }
void set_sign(sign sgn) { s = sgn; }
}
}

#endif /// __NAMESPACEINT_H

namespace_math.h -- 定义名字空间Math

// c10: namespace_math.h
#ifndef __NAMESPACEMATH_H
#define __NAMESPACEMATH_H

#include "namespace_int.h"

namespace Math {
using namespace int;
Integer a, b;
Integer divide(Integer, Integer);
}

#endif /// __NAMESPACEMATH_H

arithmetic.cpp

// c10: arithmetic.cpp
#include "namespace_math.h"
void arithmetic()
{
using namespace Int;
Integer x;
x.set_sign(positive);
}

int main() {}

在arithmetic()的方法中,我们看到了using指令,如果没有他,任何命名空间的内容都需
要全名提供。

五、C++中的static数据成员

// c10: static_init.cpp
#include <iostream>
using namespace std;

int x = 100;

class with_static {
static int x;
static int y;
public:
void print() const {
cout << "with_static::x = " << x << endl;
cout << "with_static::y = " << y << endl;
}
};

int with_static::x = 1;
int with_static::y = x + 1;
// with_static::x not ::x

int main()
{
with_static ws;
ws.print();
}
/*
with_static::x = 1
with_static::y = 2
*/

类的static成员存在于一个独立的空间中,无论建立多少个对象,他们的static成员都是共
享的。更重要的是static成员只有在类对象中可见,它也可以受成员访问符约束。

在此例子中,在没有创建实例的时候,我们就对with_static::x进行初始化,这对于静态成
员是可行的。


// c10: static_array.cpp
class values {
static const int scsize;// = 100;
static const long sclong = 100;

static const int scints[];
static const long sclongs[];
static const float sctable[];
static const char scletters[];
static int size;
static const float scfloat; // differ from tic++
static float table[];
static char letters[];
};
int values::scsize = 100;
int values::size = 100;
const float values::scfloat = 1.1;
const int values::scints[] = { 99, 47, 33, 11, 7 };
const long values::sclongs[] = { 99, 47, 33, 11, 7 };
const float values::sctable[] = { 1.1, 2.2, 3.3, 4.4 };
const char values::scletters[] = { 'a','b','c','d','e','f','g','h','i','j',};
float values::table[4] = { 1.1, 2.2, 3.3, 4.4 };
char values::letters[10] = { 'a','b','c','d','e','f','g','h','i','j',};

int main() { values v; }

这里揭示了static初始化的可行性分析:
1) static 不可以在类中初始化,必须要求const。
2) static const 非原子类型,不可以在类中初始化。
3) static const 原子类型,可以而且必须在类中初始化。

下面来看嵌套类定义和局部类定义中的static情况————

// c10: local.cpp
// static member & local classes
#include <iostream>
using namespace std;

// Nested classes CAN have static data members;
class outer {
class inner {
static int i;
};
};

int outer::inner::i = 47;

//local class can not have static data members;
void f()
{
class local {
public:
//! static int i; // error
// (how could you define i?)
} x;
}

int main()
{
outer x;
f();
}

你可以发现嵌套类定义中的static完全可行,但是在局部类定义中却被禁止了。
我们怎么办呢?实际上,局部类定义是很少使用的。

六、static成员方法

// c10: static_member_funcs.cpp
class X {
int i;
static int j;
public:
X(int ii = 0): i(ii) {
// non-static member function can
// access static member function or data
j = i;
}
int val() const { return i; }
static int incr() {
//! i++; // Error: static member function
// cannot access non-static member data
return ++j;
}
static int f() {
//! val(); // Error: static member function
// cannot access non-static member function
return incr();
}
};

int X::j = 0;

int main()
{
X x;
X *xp = &x;

x.f();
xp->f();
X::f(); // only works with static members
} ///:~

同样,我们在C++中可以像定义成员数据那样定义一个static成员方法,在我们需要创建一
个仅仅作用于类或者对象的方法的时候,我们不希望他在全局区域内造成命名污染,因此,
这个念头就诞生了。

static方法可以直接被引用调用,不需要建立类实例(就像数据成员初始化一样)。有一点
要注意的是:static成员方法,他并不包含this指针,这注定了它:

* 不能够访问数据成员,也不能调用成员方法。


// c10: singleton.cpp
#include <iostream>
using namespace std;

class egg {
static egg e;
int i;

egg(int ii): i(ii) {}
egg(const egg&); // prevent copy-construction
public:
static egg* instance() { return &e; }
int val() const { return i; }
};

egg egg::e(47);

int main()
{
//! egg x(1); // private constructor
cout << egg::instance()->val() << endl;
}

这个例子非常有趣,除了egg::e,你将永远无法创造第二个egg对象。

1) 因为egg的构造函数设置为私有,你无法直接创建私有对象。
2) 使用static成员方法egg::instance(),你可以创建一个实例,这个实例是
static egg e;
3) 由于egg e本身也是e,那么所有的 instance()调用,都会返回这一个对象。其他对象创
建不能。
4) 此例还禁用了拷贝构造方法 egg(const egg&),这意味着,你甚至无法赋值对象,最后
一条路也被封锁了。

七、static 初始化依赖 static initialization dependency
initializer.h

// c10: initializer.h
#ifndef __INITIALIZER_H
#define __INITIALIZER_H

#include <iostream>

extern int x;
extern int y;

class initializer {
static int init_count;
public:
initializer() {
std::cout << "initializer()" << std::endl;
//initialize first time only
if (init_count++ == 0) {
std::cout << "performing initialization"
<< std::endl;
x = 100;
y = 200;
}
}
~initializer() {
std::cout << "initializer()" << std::endl;
//cleanup last time only
if (--init_count == 0) {
std::cout << "performing cleanup"
<< std::endl;
// any necessary cleanup here
}
}
};

static initializer init;

#endif//__INITIALIZER_H

initializer_defs.cpp -- 已知模块

// c10: initializer_defs.cpp
#include "initializer.h"

int x;
int y;
int initializer::init_count;

initializer.cpp -- 测试模块(main)

// c10: initializer.cpp
#include "initializer.h"
using namespace std;

int main()
{
cout << "inside main()" << endl;
cout << "leaving main()" << endl;
}
/* result:
initializer()
performing initialization
initializer()
inside main()
leaving main()
initializer()
initializer()
performing cleanup
*/

在extern的初始化过程中,我们会遇到这样的问题:

extern std::ofstream out;
class Oof {
public:
Oof() { std::out << "ouch"; }
} Oof;

这段代码引用到了其他文件的一个对象ofstream out,如果两个文件位于不同的模块,那么
这意味着out的定义和使用位于两个模块。在编译器编译的时候,往往不能完全决定哪一个
模块先编译,操作系统也没有提供确保初始化顺序的绝对方法。如果是此段代码先编译,那
么面对的问题就是,out还未构造就使用了,这会带来混乱。
这个问题针对的对象是(它们会在main()之前执行初始化):
1) 全局对象
2) 局部static对象

为了解决此问题的发生,我们给出三种方法
1) 永远别这样做,避免此类问题发生是最好的选择
2) 如果必须这样做,把静态对象定义放在一个文件里,这样你可以安排预想的编译次序。
3) 如果不可避免的要把静态对象放在不同模块中,可以采用两种技巧来解决此问题。

本段例子就是技巧1,它是Jerry Schwarz在创建iostream库的时候提出的。
initializer.h *设计目标
initializer_defs.cpp *已知模块
initializer.cpp *已知模块
我们要求的目标是,无论initializer_defs.cpp和initializer.cpp哪一个先初始化,都可
以保证x,y的安全使用。
实现方法:首先在initializer.h中,extern引用x,y,然后定义为x,y专门的初始化类
initializer,根据static成员int_count是否为0,判断x,y是否已经被初始化,如果未初始
化,则实现初始化;否则不要动。以同样的方法定义析构方法。接着在文件末尾添加static
对象initializer init。
最后一步,在x,y对象定义的模块初始化 initializer::init_count = 0;

由于已知模块都已经包含了initializer.h头文件,那么他们将共同拥有init对象,init对
象保证了对象x,y的初始化;由于class initializer的处理,也将屏蔽x,y的重复初始化。

下面介绍技巧2————
dependency1.h

// c10: dependency1.h
#ifndef __DEPENDENCY1_H
#define __DEPENDENCY1_H

#include <iostream>

class dependency1 {
bool init;
public:
dependency1(): init(true) {
std::cout << "dependency1 construction"
<< std::endl;
}
void print() const {
std::cout << "dependency1 init:"
<< init << std::endl;
}
};

#endif//__DEPENDENCY1_H

dependency2.h

// c10: dependency2.h
#ifndef __DEPENDENCY2_H
#define __DEPENDENCY2_H

#include "dependency1.h"

class dependency2 {
dependency1 d1;
public:
dependency2(const dependency1 &dep1): d1(dep1) {
std::cout << "dependency2 construction ";
print();
}
void print() const { d1.print(); }
};

#endif//__DEPENDENCY2_H

dependency1statfun.h

// c10: dependency1statfun.h
#ifndef __DEPENDENCY1STATFUN_H
#define __DEPENDENCY1STATFUN_H

#include "dependency1.h"
extern dependency1 &d1();

#endif//__DEPENDENCY1STATFUN_H

dependency2statfun.h

// c10: dependency2statfun.h
#ifndef __DEPENDENCY2STATFUN_H
#define __DEPENDENCY2STATFUN_H

#include "dependency2.h"
extern dependency2 &d2();

#endif//__DEPENDENCY2STATFUN_H

dependency1statfun.cpp

// c10: dependency1statfun.cpp
#include "dependency1statfun.h"

dependency1 &d1()
{
static dependency1 dep1;
return(dep1);
}///:~

dependency2statfun.cpp

// c10: dependency2statfun.cpp
#include "dependency1statfun.h"
#include "dependency2statfun.h"

dependency2 &d2()
{
static dependency2 dep2(d1());
return(dep2);
}

technique2b.cpp

// c10: technique2b.cpp
// (L) dependency1statfun dependency2statfun

#include "dependency2statfun.h"

int main()
{
d2();
}///:~
/* result:
dependency1 construction
dependency2 construction dependency1 init:1
*/

这个方法显然更加干净清爽:)
dependency1.h * 已知头文件
dependency2.h * 已知头文件
dependency1statfun.h * 设计头文件
dependency2statfun.h * 设计头文件2
dependency1statfun.cpp * 设计模块
dependency2statfun.cpp * 设计模块2
technique2b.cpp *main函数,用来测试
我们发现class dependency2的对象实例必须通过拷贝构造函数来初始化,而且他需要借助
class dependency1的对象。因此以下的初始化总是可能存在的:
dependency2 dep2((dependency1)dep1);
如果dep1和此定义不再同一模块中,我们显然要面对静态对象的依赖问题。

技巧2是这样作的。他通过定义两个模块提供了两个函数d1()和d2(),他们分别包含一个静
态对象dep1和dep2(d1),如果要创建dependency2对象,只有通过d2()返回得到。实际上,
技巧2正是封锁了初始化方法,迫使初始化按照即定的顺序进行。
dependency2& -> d2() -> dep2(d1()) -> d1() -> dep1 -> dependency1: dependency1()

总结

我们看到static带来功能性的同时,也带来了困惑,因为在某些情况下,它用来控制存储位
置,有些时候控制可视范围或者名字的链接属性。
* static存储在静态数据内存区,拥有全局生命域
* 作用在局部变量上,static使得变量局部可见却拥有全局生命期。
* 作用在全局变量上,static控制可视范围,仅仅在本模块内可见。
* 作用在类的数据成员上,static既控制生命期,又限制该标识符仅仅在类中可见。
* 作用在类的成员方法上,static方法除了保留数据成员特性以外,还去除了指针。这意味
着,静态成员不能访问非static数据成员,也不能调用非static成员方法。

namespace 同样给予更加灵活的方法,让您在大工程内部控制代码的增加和繁衍。

class 内部的class是在程序中控制名字的另外一种方法,他不会和全局名字冲突,而在程
序内部享用独立的可视范围和访问控制。这会大大增强你代码的维护性能。

2008年10月6日星期一

[TIC++] C9. Inline Functions

代码阅读 <Thinking in C++>
Chapter 9. Inline Functions

一、什么是Inline

// c09: macro_side_effects.cpp
#include <fstream>
using namespace std;

#define BAND(x) (((x) > 5 && (x) < 10) ? (x) : 0)

int main()
{
ofstream out("macro.out");
// assure(out, "macro.out");
for (int i = 4; i < 11; i++) {
int a = i;
out << "a = " << a << endl << "\t";
out << "BAND(++a)" << BAND(++a) << endl;
out << "\t a = " << a << endl;
}

}
/* macro.out:
a = 4
BAND(++a)0
a = 5
a = 5
BAND(++a)8
a = 8
a = 6
BAND(++a)9
a = 9
a = 7
BAND(++a)10
a = 10
a = 8
BAND(++a)0
a = 10
a = 9
BAND(++a)0
a = 11
a = 10
BAND(++a)0
a = 12
*/

第一个例子,介绍了宏(macro)带来的负面效应。BAND(x)定义了一个三元运算符,C/C++中
会对选择语句中的与运算和或运算进行优化,(x)>5为false之后,将不会考虑(x)<10,而自
增运算符结合macro会产生预料之外的效果。

为了消除奇异,避免此类情况发生,我们引入了内联方法——

// c09: inline.cpp
#include <iostream>
#include <string>
using namespace std;

class Point {
int i, j, k;
public:
Point(): i(0), j(0), k(0) {}
Point(int ii, int jj, int kk)
: i(ii), j(jj), k(kk) {}
void print(const string &msg = "") const
{
if (msg.size() != 0)
cout << msg << endl;
cout << "i = " << i << ","
<< "j = " << j << ","
<< "k = " << k << endl;
}
};

int main()
{
Point p, q(1,2,3);
p.print("value of p");
q.print("value of q");
}

这里和以前并没有什么异常,这证明人的习惯是很可怕的……我们要说的是,以前在类中定义
的所有函数,都是内联函数(inline functions)。内联函数也只是存在于编译期内,编译的
时候,编译器会打开内联函数的代码,直接迭代到调用处。他起到了宏的作用,而且避免了
负面效用。

考虑一下print(const string&)方法,如果没有内联,那么print代码本身就要将this指针
放入堆栈,并且使用一次汇编语句CALL,大多数机器中,这段代码的长度会比内联展开的代
码量要大,而且花费时间也会更长。

二、使用内联
这个例子包含了三个代码段stash4.h,stash4.cpp,stash4test.cpp。

stash4.h

// c09: stash4.h
// inline functions

#ifndef __STASH4_H
#define __STASH4_H

class stash {
int size;
int quantity;
int next;
unsigned char *storage;
void inflate(int increase);
public:
stash(int sz): size(sz), quantity(0), next(0), storage(0) {}
stash(int sz, int init_quantity): size(sz), quantity(0), next(0), storage(0)
{
inflate(init_quantity);
}

~stash()
{
if (storage != 0)
delete []storage;
}

void *fetch(int index) const
{
if (index >= next)
return 0;
return &(storage[index * size]);
}

// declarations of non-inline functions
int add(void *element);
int count() const { return next; }
};


#endif // __STASH4_H

stash4.cpp

// c09: stash4.cpp
#include "stash4.h"
#include <iostream>
#include <cassert>
using namespace std;

const int increment = 100;

int stash::add(void *element)
{
if (next >= quantity)
inflate(increment);
int start_bytes = next * size;
unsigned char *e = (unsigned char*)element;

for (int i = 0; i < size; i++)
storage[start_bytes + i] = e[i];
next ++;
return(next -1);
}


void stash::inflate(int increase)
{
assert(increase >= 0);
if (increase == 0)
return;
int new_quantity = quantity + increase;
int new_bytes = new_quantity * size;
int old_bytes = quantity * size;
unsigned char *b = new unsigned char[new_bytes];

for (int i = 0; i < old_bytes; i++)
b[i] = storage[i];
delete [](storage);
storage = b;
quantity = new_quantity;
}
///:~

stash4test.cpp

// c09: stash4test.cpp
#include "stash4.h"
#include <fstream>
#include <iostream>
#include <string>
using namespace std;

int main()
{
stash int_stash(sizeof(int));

for (int i = 0; i < 100; i++)
int_stash.add(&i);
for (int j = 0; j < int_stash.count(); j++)
cout << "int_stash.fetch(" << j << ") = "
<< *(int*)int_stash.fetch(j)
<< endl;

const int bufsize = 80;
stash string_stash(sizeof(char) * bufsize, 100);
ifstream in("stash4test.cpp");
string line;
while(getline(in, line))
string_stash.add((char*)line.c_str());

int k = 0;
char *cp;
while ((cp = (char*)string_stash.fetch(k++)) != 0)
cout << "string_stash.fetch(" << k << ") = "
<< cp << endl;
}
/* result: 结果中途省略
int_stash.fetch(0) = 0
int_stash.fetch(1) = 1
int_stash.fetch(2) = 2
int_stash.fetch(3) = 3
...
int_stash.fetch(98) = 98
int_stash.fetch(99) = 99
string_stash.fetch(1) = // c09: stash4test.cpp
string_stash.fetch(2) = #include "stash4.h"
string_stash.fetch(3) = #include <fstream>
string_stash.fetch(4) = #include <iostream>
string_stash.fetch(5) = #include <string>
string_stash.fetch(6) = using namespace std;
string_stash.fetch(7) =
string_stash.fetch(8) = int main()
string_stash.fetch(9) = {
...
string_stash.fetch(31) = }
*/

stash中的简单方法在这里用到了inline,他们更加的高效和简洁。但也要留意两个最大的
方法在外部被定义,这样他们就不是inline方法,因为这样作不会带来任何效益。

三、向前引用特性

// c09: evaluation_order.cpp
// inline evaluation order

class forward {
int i;
public:
forward(): i(0) {}
// call undeclared function
int f() const { return g() + 1; }
int g() const { return 1; }
};

int main()
{
forward fwd;
fwd.f();
}

注意到,f()在g()声明或者定义之前调用了g(),这在inline函数世界里变得可能了。
_

2008年10月5日星期日

[TIC++] C8. Constants

代码阅读<Thinking in C++>
Chapter 8. constants

一、介绍常量

//safecons.cpp
#include <iostream>
using namespace std;

const int i = 100;
const int j = i + 10;
long address = (long)&j;
char buf[j + 10];

int main(void)
{
cout << "type a character & CR: ";
const char c = cin.get();
const char c2 = c + 'a';
cout << c2 << endl;
}

1. 宏,它只存在于预编译期(preprocessing),不占据内存空间,不包含类型信息。
2. 常量,也在编译期被翻译。通常情况下默认为内部链接,并且不会为const分配内存。这
被称为const folding原则。但是这个原则在很多情况下会被打破。
特例有很多,主要一点就是常量的外部引用。比如extern声明会强制建立外部链接,这
要求修饰的const也要提供地址(内存空间所在位置)。

二、常量遭遇指针

// c08: const_pointer.cpp
const int *u; // pointer to const int, init not required
int const *v; // still pointer to const int!
int d = 1;
int * const w = &d; // const pointer
const int * const x = &d; // const pointer to const object
int const * const x2 = &d;

int main(void){}

指针也可以被声明常量,这里带来两个歧义:一个是指针指向的东西被声明常量(pointer
to const);另一个是指针包含的地址本身被定义为常量(const pointer)。
上面的例子就列举了两种定义,还有另外指向常量的常量指针。
1) 指向常量的指针(`const*')
可以不被初始化
2) 常量型指针(`*const')
必须初始化
3) 指向常量的常量指针(`const * const')
必须初始化


// c08: pointer_assignment.cpp
int d = 1;
const int e = 2;
int *u = &d;
//! int *v = &e; // illegal -- a const
int *w = (int*)&e; // legal but bad practice

int main(){}

这里举例说明了指针赋值。
1. 常量不可以再被非常量直接引用(指针和引用均不可以)。
2. 但是他可以被强制转换,转换的安全性需要程序员负责。
3. 不可以在文件域内部对指向常量指针赋值(p2c,cp都不行)

三、常量返回值 return consts by value

// c08: constval.cpp
// return consts by value
// has no meaning for built-in type
int f3() { return 1;}
const int f4() { return 1;}

int main(void)
{
const int j = f3(); // Works fine
int k = f4(); // But this works fine too!
}

原子类型之间的赋值,只是拷贝,勿需考虑常量情况。


// c08: const_return_values.cpp
// const return by value
// result cannot be used as an lvalue

class X {
int i;
public:
X(int ii = 0);
void modify();
};

X::X(int ii)
{
i = ii;
}

void X::modify()
{
i++;
}

X f5()
{
return X();
}

const X f6()
{
return X();
}

void f7(const X& x) // pass non-const reference
{
x.modify();
}

int main()
{
f5() = X(1);
f5().modify();
// compile-time error
//! f6() = X(1);
//! f6().modify();
//! f7(f5());
//! f7(f6()); // 29
}

将返回值定义为常量,这对于原子类型来说没有意义。而在自定义类型中,却有很大不同。
NOTE!
如果函数返回一个常量类型的类对象,那么返回的值将不能成为左值(不可被定义和修改)。
而原子类型本身就不能成为左值,所以返回常量原子类型是无意义的。
f5()返回了非常量型,而f6()返回的是常量型的类对象,因此f6()的赋值和修改都是非法的
(f6不可以作为左值)。

四、传递和返回地址 passing and returning addresses

// c08: const_pointer2.cpp

void t(int*) {}

void u(const int *cip)
{
//! *cip = 2; // illegal -- modifies value
int i = *cip; // OK -- copies value
//! int *ip2 = cip; // illegal: non-const
}

const char *v()
{
// return address of a static array
return "result of function v()";
}

const int * const w()
{
static int i;
return &i;
}

int main()
{
int x = 0;
int *ip = &x;
const int *cip = &x;

// passing addresses
t(ip); // non-const* to non-const*
//! t(cip); // const* to non-const*
u(ip); // non-const* to const*
u(cip); // const* to const*

// returning addresses
//! char *cp = v(); // const* to non-const*, not OK
const char * ccp = v(); // const* to const*
//! int *ip2 = w(); // const* to non-const*, not OK!
const int * const ccip = w(); // const* to const*
const int * cip2 = w();
//! *w() = 1; // modify consts, not OK
}

无论是传递地址,还是返回地址。从const*到non-const*的传递(或赋值)都是被禁止的,
而如果选择将const转换为non-const,则需要确保转换的安全性。

五、常量成员的初始化

// c08: const_init.cpp
#include <iostream>
using namespace std;

class Fred {
const int size;
public:
Fred(int sz);
void print();
};

Fred::Fred(int sz) : size(sz) {}
void Fred::print() { cout << size << endl; }

int main()
{
Fred a(1), b(2), c(3);

a.print(), b.print(), c.print();
}

这里展示了构造方法的初始化列表的用法。形如:
Fred::Fred(int sz) : size(sz) {}
函数列表括弧后的单位就是,常量初始化的地方。唯有这里可以将类中定义的常量成员初始
化,实际上,他们是在进入函数体之前就进行了初始化。

六、const 成员遭遇 static

// c08: string_stack.cpp
// using static const to create a compile-time constant
// inside a class
#include <cstring>
#include <string>
#include <iostream>
using namespace std;

class string_stack {
static const int size = 100; // a static constant
const string *stack[size];
int index;
public:
string_stack();
void push(const string *s);
const string *pop();
};

string_stack::string_stack():index(0)
{
memset(stack, 0, size * sizeof(string*));
}

void string_stack::push(const string *s)
{
if (index < size)
stack[index++] = s;
}

const string *string_stack::pop()
{
if (index > 0) {
const string *rv = stack[--index];
stack[index] = 0;
return rv;
}
return 0;
}

const string icecream[] = {
"pralines & cream",
"fudge ripple",
"jamocha almond fudge",
"wild mountain blackberry",
"raspberry sortbet",
"lemon swirl",
"rocky road",
"deep chocolate fudge"
};

const int icsz = sizeof(icecream) / sizeof(*icecream);

int main()
{
string_stack ss;
for(int i = 0; i < icsz; i++)
ss.push(&icecream[i]);
const string *cp;
while((cp = ss.pop()) != 0)
cout << *cp << endl;
}

这里展示了C++ string的强大与方便。但是更重要的是,解释了compile-time constants的
用法。
static类成员意味着,无论用此类建立多少个对象,static成员实例都只会存在一个。
另外static const的特性要求,static const常量(原子型)必须在定义位置初始化。

从例子中可以看到,push持有const string*参数,pop()则返回const string*,还有
string_stack包含了一个const string*成员。这看来像一个锁链一样,一环套一环。
stack_stack包含const string *stack[],这要求在pop():
* 中间赋值必须用const传递, const string *rv;
* 返回值类型必须是const.

const string *icecream也要求push():
* 持有const参数

三个const约定了字符串以const形式亚栈,而除了push和pop以外,你再也无法更改
成员*stack[]。

七、const 成员方法

// c08: const_member.cpp
class X {
int i;
public:
X(int ii);
int f() const; // const member func
int g();
};


X::X(int ii): i(ii) {}
int X::f() const { return i;}
int X::g() {}

int main()
{
X x1(10);
const X x2(20);

x1.f();
x2.f();
//! x2.g(); // discards qualifiers
}

const成员方法,将会告诉编译器,他可以被常量对象调用。而没有被申明常量的方法,将
不可以被常量对象调用!

const成员方法和const函数(const func)有本质不同:
* const func 是返回一个常量值。
* func const 则是表明此成员方法可以被常量对象调用。他的使用方法是:

为了强调const方法,编译器强迫在const成员方法的声明和定义处都要加上const。

接下来细细观察const成员方法和普通成员方法的不同——


// c08: quoter.cpp
// random quote selection

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

class quoter {
int lastquote;
public:
quoter();
int get_lastquote() const; // const member func
const char *quote(); // member func which returns a const pointer
};

quoter::quoter()
{
lastquote = -1;
srand(time(0));
}

int quoter::get_lastquote() const
{
return lastquote;
}

const char *quoter::quote()
{
static const char *quotes[] = {
"Are we having fun yet?",
"Doctors always know best",
"Is it ... Atomic?",
"Fear is obscene",
"There is scientific evidence "
"to support the idea "
"that life is serious",
"Things that make us happy, make us wise",
};
const int qsize = sizeof quotes / sizeof *quotes;
int qnum = rand() % qsize;

while (lastquote >=0 && qnum == lastquote)
qnum = rand() % qsize;
return quotes[lastquote = qnum];
}

int main()
{
quoter q;
const quoter cq;

cq.get_lastquote();
//! cq.quote(); // 常量型对象无法调用非常量型成员方法
for (int i = 0; i < 20; i++)
cout << q.quote() << endl;
}

这里比较了const对象和普通对象处理的区别,另外揭示了const成员方法的条件:
如果成员方法体内更改了数据成员,那么它就不能声明为const。

比如const char *quoter::quote()更改了成员 lastquote(return语句),它将不能声明
为const;而int quoter::get_lastquote()则没有动,他就可以成为const成员方法,而且
可以被常量对象cq安全调用。

如果我确实声明了const成员方法,我又确实想更改类成员,我该如何是好?

八、bitwise vs. logical const

// c08: castaway.cpp
class Y {
int i;
public:
Y();
void f() const;
};

Y::Y(): i(0) {}

void Y::f() const
{
//! i++;
((Y*)this)->i++;
// Better: use C++ explicit cast syntax:
(const_cast<Y*>(this))->i++;
}

int main()
{
const Y yy;
yy.f();
}

bitwise const意思是说,此对象的每一数据比特都是永久不可变的,这样它的任何一部分
都不可改变;而logical const意思是,虽然从概念上说整个对象不可改变,但是可以从成
员基础上更改它本身。
这其实是编译器给const定义的两面性,一方面编译器小心翼翼地确保const对象是bitwise
的,另一方面,它又提供两个路径来通过_const成员函数_来更改数据成员。

以上就是第一个:转换掉const属性。无论是C还是C++语法,都一样可行。
这样带来的一个问题就是,你无法确认你的更改是否有效。。。

于是提供了第二个方法:


// c08: mutable.cpp
class Z {
private:
int i;
mutable int j;
public:
Z();
void f() const;
};

Z::Z(): i(0), j(0) {}

void Z::f() const
{
//! i++; // error - const mem func
j++;
}

int main()
{
const Z zz;
zz.f();
}

mutable 关键词告诉编译器,被定义为mutable的数据成员,在const成员方法内仍旧可以更
改。相对转型,显然mutable更加优雅一点。

TIC++也提到了只读属性(ROMability),如何指导编译器把const对象放到只读内存中呢?
这个条件比较复杂,任何可以改写数据成员的可能都会阻止只读化:
1) 不包含logical const特性(不能有mutable关键词,也不能有const转型)
2) class或者struct不可以包含任何自定义构造和析构方法
3) 也不可以包含有自定义构造和析构方法的超类和成员对象(子类没有限制)

九、volatile和类

// c08: volatile.cpp

class comm {
const volatile unsigned char byte;
volatile unsigned char flag;
enum { bufsize = 100 };
unsigned char buf[bufsize];
int index;
public:
comm();
void isr() volatile;
char read(int index) const;
};

comm::comm(): index(0), byte(0), flag(0) {}

// only a demo; won't actually work as an interrupt service routine:
void comm::isr() volatile
{
flag = 0;
buf[index++] = byte;
if (index >= bufsize)
index = 0;
}

char comm::read(int index) const
{
if (index < 0 || index >= bufsize)
return 0;
return buf[index];
}

int main()
{
volatile comm port;
port.isr(); // OK
//! port.read(0); // Error, read() not volatile
}

volatile关键字和const是极其相似的修饰词,只不过他们的意义不同。
通常情况下,编译器会告诉我们:我把此数据读入到了寄存器,但我保证不会再去碰它。下
次需要读取此变量,直接从寄存器中读取即可(而不需要到内存中寻找)。
而volatile告诉编译器:这个数据成员的活动,将会超过编译器的理解范围。如果需要读取
此数据,不要尝试从寄存器中取值,因为内存和寄存器可能已经出现了不同步。

同const一样,volatile对象也只能调用volatile成员方法。

十、总结
const一章的例子比其他章节要多,原因是const类型在C++中遇到了多种繁杂的情况,而且
他们也表现出来各自的特性。总结来说,我们探讨了以下情况:

* 原子类型申明为const(这里也讨论了const*和*const)
const声明意味着,这意味着该内存为只读属性。const存在于编译期,通常情况下默认为
内部链接,并且不会为const分配内存,这被称为const folding原则。const类型的局部
标识符必须在定义时候初始化。
const指针包括了指向常量的指针(const*)和常指针(*const),const*可以选择是否初始
化,但是他不可以显性修改指针指向的内存(*x = y illegal);*const不可以修改指针
本身((int*)a = (int*)b illegal)。

* 返回值为const
const返回值将不可作为左值,赋值修改都是非法的。而作为右值时候,const返回值的左
值必须为const,const到non-const的传递和赋值都是非法的。

* 传递参数为const
const到non-const的传递仍旧是非法的。要切记这一规则。

* 成员数据为const
const成员数据不可以在普通成员方法中更改。这里也给出了特例(const_init.cpp):
使用构造方法的初始化列表来完成常量数据成员的初始化。

* 成员数据为static const
static说明所有的成员实例都会共享一个static数据成员。static const额外要求必须在
定义位置实现初始化。

* 成员方法为const
首先,一个const成员方法将永远不会更改类的数据成员。
再次,声明为const的成员方法允许被const对象调用,而普通成员方法是不允许调用的。

* 对象为const
const对象表现出两个特性,一个是表面上他的整体不可改变(bitwise const),另一个
则是通过某些特殊方法实现更改(logical const)。
logical const的实现有两种方法,一个在const成员方法使用强制类型转换
(castaway.cpp),另一个是在类定义中给想要修改的值添加mutable修饰词
(mutable.cpp)。
_

2008年10月4日星期六

[TIC++] C7. Function Overloading and Default Arguments

代码阅读<Thinking in C++>
Chapter 7. Function Overloading and Default Arguments

一、C++中的union

//unionclass.cpp
#include
using namespace std;

union u {
private:
int i;
float f;
public:
u(int a);
u(float b);
~u();
int read_int();
float read_float();
};

u::u(int a)
{
i = a;
}

u::u(float b)
{
f = b;
}

u::~u(void)
{
cout << "u::~u()\n"; } int u::read_int(void) { return i; } float u::read_float(void) { return f; } int main(void) { u X(12), Y(1.9F); cout <<>

这个例子展示了union的用法。union仍就是C++的关键词,union也和class一样,他也可以
有constructor,destructor,成员方法甚至访问控制。
union和class的不同之处在于:
1. union含有独特的数据存储方式。
2. union不能被继承,这也是由特性1决定的。这使得它在C++里无法大展身手。

二、匿名union

//supervar.cpp
#include
using namespace std;

class supervar {
enum {
character,
integer,
floating_point,
} vartype;
union { // anonymous union
char c;
int i;
float f;
};
public:
supervar(char ch);
supervar(int ii);
supervar(float ff);
void print();
};

supervar::supervar(char ch)
{
vartype = character;
c = ch;
}

supervar::supervar(int ii)
{
vartype = integer;
i = ii;
}

supervar::supervar(float ff)
{
vartype = floating_point;
f = ff;
}

void supervar::print(void)
{
switch (vartype) {
case character:
cout << "character: " <<>

1. class supervar定义了一个enum类型,但是没有给此类型标注类型名。因为他只是用此
enum定义了一个变量实例vartype.
2. class supervar定义了一个union类型成员,这是一个匿名union(anonymous union),这
样在此类的成员中应用union成员,将不必使用成员操作符`.'。
如果匿名union定义在文件类型,_一定_要声明为static,这样他就有了内部链接。
_

2008年9月26日星期五

[TIC++] C5. Hiding the implementation

代码阅读<Thinking in C++>
Chapter 5. Hiding the implementation

一、友元


//friend.cpp

// public, private, protected
// friends

struct X;
struct Y {
void f(X*);
};


struct X {
private:
int i;
public:
void initialize();
friend void Y::f(X*); // struct member friend
friend struct Z; // Entire struct is a friend
friend void g(X*, int); // Global friend
friend void h();
};

void X::initialize()
{
i = 0;
}

void Y::f(X *x)
{
x->i = 47;
}

struct Z {
private:
int j;
public:
void initialize();
void g(X *x);
};

void Z::initialize()
{
j = 99;
}

void Z::g(X *x)
{
x->i += j;
}


void g(X *x, int i)
{
x->i = i;
}

void h()
{
X x;
x.i = 100;
}

int main()
{
X x;
Z z;
z.g(&x);
}

此代码示范了friend友元的用途,声明友元的类在告诉大家,此友元可以访问我的私有成员。
* 从struct X的定义可以看出,在public区域定义了四种友元,他们都可以试图访问修改私
有成员i,当然也包括自己的成员函数。

全局友元函数 global friend
在函数体内,可以任意修改struct X类对象的成员i。
类成员作为友元 struct member friend
可以更改传递的X*参数。
整个类作为友元 entire struct
这样此类的所有成员函数,都拥有友元性质。

二、嵌套的友元


//nested_friend.cpp

#include <iostream>
#include <cstring>
using namespace std;

const int sz = 20;

struct Holder {
private:
int a[sz];
public:
void initialize();
struct Pointer;
friend struct Pointer;
struct Pointer { // nested struct friend
private:
Holder *h;
int *p;
public:
void initialize(Holder *h);
void next();
void previous();
void top();
void end();
int read();
void set(int i);
};
};

// type class::nested_class::variable;
void Holder::initialize()
{
memset(a, 0, sz * sizeof(int));
}

void Holder::Pointer::initialize(Holder *rv)
{
h = rv;
p = rv->a; // access private member of super class
}

void Holder::Pointer::next()
{
if (p < &(h->a[sz - 1]))
p++;
}

void Holder::Pointer::previous()
{
if (p > &(h->a[0]))
p--;
}

void Holder::Pointer::top()
{
p = &(h->a[0]);
}

void Holder::Pointer::end()
{
p = &(h->a[sz-1]);
}

int Holder::Pointer::read()
{
return *p;
}

void Holder::Pointer::set(int i)
{
*p = i;
}

int main()
{
Holder h;
Holder::Pointer hp, hp2;
int i;

h.initialize();
hp.initialize(&h);
hp2.initialize(&h);
for (i = 0; i < sz; i++) {
hp.set(i);
hp.next();
}
hp.top();
hp2.end();
for (i = 0; i < sz; i++) {
cout << "hp = " << hp.read()
<< ", hp2 = " << hp2.read() << endl;
hp.next();
hp2.previous();
}
}
/* result:
hp = 0, hp2 = 19
hp = 1, hp2 = 18
hp = 2, hp2 = 17
hp = 3, hp2 = 16
hp = 4, hp2 = 15
hp = 5, hp2 = 14
hp = 6, hp2 = 13
hp = 7, hp2 = 12
hp = 8, hp2 = 11
hp = 9, hp2 = 10
hp = 10, hp2 = 9
hp = 11, hp2 = 8
hp = 12, hp2 = 7
hp = 13, hp2 = 6
hp = 14, hp2 = 5
hp = 15, hp2 = 4
hp = 16, hp2 = 3
hp = 17, hp2 = 2
hp = 18, hp2 = 1
hp = 19, hp2 = 0
*/

这里的Holder类中又包含Pointer类定义,这是个嵌套类。例子在展示子类作为友元时的用
途。
在这里定义了Holder类对象h,和子类Pointer对象hp,hp2,他们包含有h的指针,和父类私
有成员a的位置。作为友元类,hp和hp2可以访问、修改父类对象h的私有成员int a[];

三、初识构造函数和析构函数


//constructor.cpp
#include <iostream>
using namespace std;

class Tree {
int height;
public:
Tree(int initialHeight);
~Tree();
void grow(int years);
void printsize();
};

Tree::Tree(int initialHeight)
{
height = initialHeight;
}

Tree::~Tree()
{
cout << "inside tree destructor " << endl;
printsize();
}

void Tree::grow(int years)
{
height += years;
}

void Tree::printsize()
{
cout << "Tree height is " << height << endl;
}

int main()
{
cout << "before opening brace " << endl;
{
Tree t(12); //constructor called
cout << "after Tree creation" << endl;
t.printsize();
t.grow(4);
cout << "before closing brace" << endl;

//destructor called(endline of t)
}
cout << "after closing brace " << endl;
}
/* result:
before opening brace
after Tree creation
Tree height is 12
before closing brace
inside tree destructor
Tree height is 16
after closing brace
*/

例子展示了constructor和destructor的性能。
* constructor在建立对象的时候,由编译器插入语句,完成初始化行为,在对象生命期内
只执行一次。
* destructor相反,在注销对象的时候执行。
* 代码中使用了{}圈定对象Tree T的生命域,它在`}'之后被注销。
下面讲述constructor一个特性。


//nojump.cpp
#include <iostream>
using namespace std;

class X {
public:
X();
};

X::X() {}

void f(int i)
{
if (i < 10) {
// crosses initialization of `X x1'
// goto jump1;
}
X x1;

jump1:
switch (i) {
case 1:
X x2;
// crosses initialization of `X x3'
break;
// case 2:
X x3;
break;
}
}

int main()
{
f(9);
f(11);
}

这是一个诡异的例子,首字符注释段取消注释之后,编译器将报错: `cross
initialization'。GCC将不允许goto,case语句等,来跳过任何对象的定义(初始化)阶段。

四、定义stack


//stack.h
#ifndef _STACK_H
#define _STACK_H

class stack {
struct linklist {
void *data;
linklist *next;
linklist(void *dat, linklist* nxt);
~linklist();
}* head;
public:
stack();
~stack();
void push(void *data);
void *peek();
void *pop();
};

#endif // STACK_H


//stack.cpp
#include "stack.h"

stack::linklist::linklist(void *dat, linklist *nxt)
{
data = dat;
next = nxt;
}

stack::linklist::~linklist() {}

stack::stack()
{
head = 0;
}

void * stack::peek()
{
if (head != 0)
return head->data;
else
return 0;
}

void stack::push(void *dat)
{
head = new linklist(dat, head);
}

void * stack::pop()
{
if (head == 0)
return 0;
void *result = head->data;
linklist *oldHead = head;
head = head->next;
delete oldHead;
return result;
}

stack::~stack()
{
if (head != 0)
return;
}


//stacktest.cpp
#include "stack.h"

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
// requireArgs(argc, 1);
ifstream in(argv[1]);
if(in == 0)
return -1;
// assure(in, argv[1]);

stack textlines;
string line;
while (getline(in, line))
textlines.push(new string(line));
string *s;
while ((s = (string *)textlines.pop()) != 0) {
cout << *s << endl;
delete s;
}
}
/* 执行命令 ./a.out Makefile
* 将按行逆序打印Makefile内容。
*/

stack.h,stack.cpp,stacktest.cpp是链表式堆栈的实现。
* linklist是节点数据结构的实现,他是stack数据结构的成员。
* 无论对于父子类对象,他们都会有自己的构造函数和析构函数。
* push函数非常漂亮!
* 使用new 和 delete,来分配堆区空间,这会使得C++代码更加优雅。

2008年9月20日星期六

[TIC++] C3. C in C++

代码阅读<Thinking in C++>
Chapter 3. C in C++


一、指针运算

//ptr_math.c
#include <iostream>
using namespace std;

#define P(EX) cout << #EX << ": " << EX << endl;

int main(void)
{
int a[10];

for (int i = 0; i < 10; i++)
a[i] = i;

int *ip = a;
P(*ip);
P(*++ip);
P(*(ip + 5));

int *ip2 = ip + 5;
P(*ip2);
P(*(ip2 - 4));
P(*--ip2);
P(ip2 - ip);
}
/* result:
*ip: 0
*++ip: 1
*(ip + 5): 6
*ip2: 6
*(ip2 - 4): 2
*--ip2: 5
ip2 - ip: 4
*/

C中强大的指针在C++里得到很好的保留,指针的灵活也给初学者带来困惑
1. `#EX'宏定义,此宏定义,将直接迭代打印变量名称(而不是变量值)
2. 指针是带有其类型信息的,根据信息,编译器理解指针运算的单位。
int *pi;
char *pc = (char*)pi;
pi++; //int型指针跳转4个字节
pc++; //char指针增加1个字节

二、介绍引用

//pass_reference.cpp
#include <iostream>
using namespace std;

void f(int& r) //reference
{
cout << "r = " << r << endl;
cout << "&r = " << &r << endl;
r = 5;
cout << "r = " << r << endl;
}

int main(void)
{
int x = 47;

cout << "x = " << x << endl;
cout << "&x = " << &x << endl;
f(x);
cout << "x = " << x << endl;
}
/* result:
x = 47
&x = 0xbfbb29ac
r = 47
&r = 0xbfbb29ac
r = 5
x = 5
*/

引用是C++中的新概念,相当于给变量起了一个新名字。通常用于参数传递。
* C中允许传值,和传址两中方式,在C++又包含了传递引用。
* 传递引用和传址不一样的是,调用方只是传递变量,而非该变量的指针,被调用函数则心
领神会,隐性得到参数地址(&r = &x)。

三、C++中的类型转换

//dynamic_cast.cpp
class Base
{
public:
int m_iNum;
virtual void foo() {}; //缺少虚函数会报错
};

class Derived: public Base
{
public:
char *m_szName[100];
};

int main(void)
{
Base *pb;
Derived *pd1 = static_cast<Derived *>(pb);
Derived *pd2 = dynamic_cast<Derived *>(pb);
}

(非TIC++代码)
dynamic_cast 是C++引入的类型转换方法,其他另外还有三个标准方法,这里将举例介绍
dynamic_cast和const_cast。
static_cast是静态类型转换,他仅要求:不可以在无关类型间转换。在基类和子类的指针
和引用转换过程中,其下行转换是危险的(即便可能通过),因为static_cast不会进行类
型转换。
dynamic_cast则弥补了这个缺陷,上下行转换都会进行类型检查,不符合的时候会返回NULL
指针,表明转换失败(而不是报错)。值得注意的是,下行转换的时候dynamic_cast要求基
类保留纯虚函数,否则会报错(因为纯虚函数拥有包含运行时类型信息的虚函数表,无虚函
数是没有虚函数表的)。


//const_cast.cpp
int main(void)
{
const int i = 0;

int *j = (int*)&i; // deprecated form
j = const_cast<int*>(&i); // preferred, only int* to int*
// long *l = const_cast<long*>(&i); // int* to long*? invalid!
volatile int k = 0;
int *u = const_cast<int*>(&k);
}

const_cast<TYPE>(expression)仍然是C++的标准类型转换方法。他用来转换const和
volatile(比如常量指针转换为非常量指针)。有一个要求,那就是要求TYPE和express类
型必须一致。

[TIC++] C2. Making objects

代码阅读 <Thinking in C++>
Chapter 2. Making Objects


一、第一个C++程序

//hello.cpp---------------------
#include <iostream>
using namespace std;

// cout is an object
// `<<' is an overloaded operator
// `namespace' is used to prevent name collisionx
int main(void)
{
cout << "Hello world! "
<< "I am " << 8
<< " today!"<< endl;
}

这是C++的第一个代码程序。和大多其他语言的教材一样,Bruce Eckel也选择了`Hello
world!'这样一个程序来开启C++之门。这个程序的框架和C极其相似,有头文件包含,main
函数等等。

1. <iostream>是C++的标准头文件,这些头文件包含了标准数据结构定义和函数的索引。他
们位于: /usr/lib/gcc/i686-pc-linux-gnu/4.3.1/include
(在`gcc -v'中可以查询configure项:includedir)
2. namespace是C++引入的概念,为了避免命名污染,C++用namespace来指定一些变量的可
视范围(scope),`using namespace std'是使用std标准命名空间。
3. cout是iostream中定义的一系列标准对象,`<<'则实现了cout对象的运算符重载,作用
是将字符等对象输出到标准输出对象cout。

iostream定义标准对象有:
* cout
一个ostream类的对象,用来打印数据到标准输出设备(STDOUT)
* cerr
也是ostream类的对象,将非缓冲输出数据写入到标准错误设备(STDERR)
* clog
和cerr相似,但是使用缓冲方式输出
* cin
istream,用来从标准输入设备中读取数据(STDIN)

二、numconv.cpp

//numconv.cpp---------------------
#include <iostream>
using namespace std;

int main(void)
{
int number;

cout << "please input the number : ";
cin >> number;
cout << "value in octal = 0" << oct << number << endl;
cout << "value in hex = 0x" << hex << number << endl;
}

程序从终端读取一个整数,以八进制和十六进制将其打印,展现的是重载运算符的灵活用法。
输出说明:
1. 如果输入起始字符不是整数,则打印无规则数据。(?)
2. 如果输入起始字符是整数,则一直读取到不是字符或者换行符,然后打印。

三、文件流介绍

//scopy.cpp---------------------
#include <fstream>
#include <iostream>
using namespace std;

int main(void)
{
ifstream in("Makefile");
ofstream out("output");
string s;

// don't worry about how much storage to allocate for a string
// just add things to it!
while (getline(in, s))
out << s << "\n";
}

读取文件Makefile,按行打印到文件output(一次输出一行)。
* ifstream和ostream是来自<fstream>的类,负责建立文件流对象(读和写)。
* string是C++的内建类,这里不必再去担心如何为string对象分配空间。
* getline来源于<string>。
istream& getline( istream& is, string& s, char delimiter = '\n' );
* out如同标准对象cout一样,可以使用重载运算符`<<'。

四、介绍vector

//getwords.cpp---------------------
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

int main(void)
{
vector<string> v;
ifstream in("Makefile");

// string line;
// while (getline(in, line)) // (1)
// v.push_back(line);
string word;
while (in >> word) // (2)
v.push_back(word);

for (int i = 0; i < v.size(); i++)
cout << i << ": " << v[i] << endl;
}

使用C++标准模板类vector。
1. vector是C++的标准模板类,作为C++代码可重用性的体现,STL一直是C++中变动频繁的
地方。
2. vector<string>将临近元素像数组一样存储。
vector成员赋值和添加元素都可以在O(k)中完成,查找和插入则在O(k*n)中完成。
vector支持一系列操作(cppreference.com):
  
/* vector<TYPE>:
* void assign( size_type num, const TYPE& val );
* void assign( input_iterator start, input_iterator end );
* void insert( iterator loc, size_type num, const TYPE& val );
* void push_back( const TYPE& val );
*/

代码阅读TIC++:序章

一、硬件环境

IBM Thinkpad R50
Intel Pentium M 1400MHz


二、软件环境

Gentoo Linux 2008.0
linux-2.6.26-gentoo
GCC 4.3.1
GNU binutils 2.1.8


GCC编译器,其具体信息可以通过`gcc -v'来查询:

Using built-in specs.
Target: i686-pc-linux-gnu
Configured with:
/var/tmp/portage/sys-devel/gcc-4.3.1/work/gcc-4.3.1/configure
--prefix=/usr
--bindir=/usr/i686-pc-linux-gnu/gcc-bin/4.3.1
--includedir=/usr/lib/gcc/i686-pc-linux-gnu/4.3.1/include
--datadir=/usr/share/gcc-data/i686-pc-linux-gnu/4.3.1
--mandir=/usr/share/gcc-data/i686-pc-linux-gnu/4.3.1/man
--infodir=/usr/share/gcc-data/i686-pc-linux-gnu/4.3.1/info
--with-gxx-include-dir=/usr/lib/gcc/i686-pc-linux-gnu/4.3.1/include/g++-v4
--host=i686-pc-linux-gnu
--build=i686-pc-linux-gnu
--disable-altivec
--enable-nls
--without-included-gettext
--with-system-zlib
--disable-checking
--disable-werror
--enable-secureplt
--disable-multilib
--enable-libmudflap
--disable-libssp
--enable-cld
--disable-libgcj
--with-arch=i686
--enable-languages=c,c++,treelang,fortran
--enable-shared
--enable-threads=posix
--enable-__cxa_atexit
--enable-clocale=gnu
--with-bugurl=http://bugs.gentoo.org/
--with-pkgversion='Gentoo 4.3.1 p1.0'
Thread model: posix
gcc version 4.3.1 (Gentoo 4.3.1 p1.0)


三、关于《Thinking in C++》
《Thinking in C++》是C++非常出色的教材类书籍,作者是Bruce Eckel
(同时也是《Thinking in Java》的作者)。
你可以在这里查看到他的全面信息,同样在这里可以下载电子版图书。

四、代码编译
在下载的文件里,一般都包含有书籍里的代码。
编译他们是很简单的,通常情况下,执行一下命令,就可以得到可执行文件 a.out.

g++ filename.cpp


线程的例子需要在加上另外的POSIX thread支持的gcc选项`-pthread'。

g++ filename.cpp -pthread


多文件项目可以自己简单的写出Makefile,在此不再赘述。