侯捷c++课程笔记: 类之间的关系
OOD:object oriented design-relationships between classes
Composition 复合
表示 has a 的关系
template <class T>
class queue
{
protected:
deque<T> c;
public:
bool pop(){c.pop_front();}
//... all implemented by deque
};
上面的一段代码既是Composition,也是一种设计模式 Adapter
queue的类中有一个deque,其中所有的function都是由deque实现,自己没有添加新的功能
这就好比变压器一样,将不同的电压进行转换。queue对deque的功能实现一定的改装,呈现出一种不同的效果。
Adapt:有适配,改写,修改的意思
sizeof(queue)分析
首先queue的大小即为deque的大小
template <class T>
class deque
{
protected:
Itr<T> start;
Itr<T> finish;
T** map;
unsigned int map_size;
};
deque有两个Itr和一个T**,一个unsigned int
template <class T>
struct Itr
{
T* cur;
T* first;
T* last;
T** node;
//...
};
一个Itr内有四个指针,假设32位机器,则sizeof(queue)=2(44)+4+4=40
构造和析构
c++编译器处理好了执行的顺序
构造由内而外:Container的构造函数首先调用Component的default构造函数,然后执行自己
Container::Container(...) : Component() {...};
假如自己在外部类没有特别写出上面的格式,编译器自动生成如上格式,这里调用默认构造函数,因为是编译器自动决定的。而编译器并不知道应该调用哪种构造函数,所以选择调用默认的。如果想要指定调用,需要自己在外部的类的构造函数中指出。
析构由外而内:Container的析构函数首先执行自己,再调用Component的析构函数
Container::Container(...) {... ~Component()};
Delegation:Composition by reference
和复合的形式类似,只不过比较虚,因为拥有的不是实体,而是一个指针。
和Composition不同的一点是生命周期可能不同,可能等需要的时候才会创建Component
class StringRep
{
int count;
char* rep;
}
class String
{
StringRep* rep;
}
上面这段是一种pimpl的实现,即pointer to implementation,也叫Handle/Body。存放指针的是handle,只定义一些接口,具体的实现放在body内。
这样做的一个好处是实现类的形式是可以切换的,实现的变动不影响客户端,具有一种弹性。
同时StringRep实现了reference counting,当String类进行拷贝时,指向的将会是同一份StringRep,实现了内存的共享。
但有一点要注意的是,当有多个共享时,其中一个pointer想要修改内容,需要复制一份给它修改,否则将会影响到其他pointer。这就是copy on write的思想。
Inheritance
表示 is a 的关系,有三种继承方式,一般采用public继承
父类的数据对象是会被继承下来的
标准库的一段继承
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template <typename _Tp>
struct _List_node
: public _List_node_base
{
_Tp _M_data;
}
内存的角度看,子类拥有父类的成分。构造和析构的方式和Composition一样,同样是编译器默认安排了
如果你认为一个类有可能成为父类,应该将其析构函数设置为virtual
因为你有可能需要用一个基类的指针指向一个子类的对象,在delete时,如果不设置为virtual,将不能调用到子类的析构函数