本文主要记录我在学习Data Structures and Algorithms with Object-oriented Design Patterns in C++ 第五章《Data types and Abstraction》时关于容器及容器内元素的理解。同时,也截取了《An Introduction to Design Patterns in C++ with Qt》第六章部分关于Qt容器的内容,进行比较。
一、容器(container)
The Container class abstracts the notion of a container--an object that holds within it other objects.
容器就是一种能存放其他物质的物质。参考:点击打开链接。具体的接口声明如下:
<span style="font-size:14px;">class Container : public virtual Object, public virtual OwnerShip { public: virtual unsigned int Count() const; virtual bool IsEmpty() const; virtual bool IsFull() const; virtual HashValue Hash() const; virtual void Put(std::ostream &)const; virtual Iterator & NewIterator() const; virtual void Purge() = 0; virtual void Accept(Visitor &) const = 0; protected: Container(); unsigned int count; };</span>以上是容器的一些最基础的接口。其中,构造函数是protected类型,限定该类仅用作虚基类。此外,有两个函数需要特别注意,一是迭代器的工厂函数(NewIterator()函数),用于产生一个与具体容器匹配的迭代器(在具体的类中,会有一个迭代器成员);另一个是Accept()函数,用于接收一个外部传入的Visitor对象(这个Visitor模式,类似于函数指针作为函数形参的作用,即机制与策略分离)。下面在展示一个容器的一些扩展接口:
<span style="font-size:14px;">class SearchableContainer : public virtual Container { public: virtual bool IsMemeber(Object const &) const = 0; virtual void Insert(Object &) = 0; virtual void Withdraw(Object &) = 0; virtual Object & Find(Object const &) const = 0; };</span>
Direct Containment —— When an object is put into a container, a copy of that object is made in the container.
Indirect Containment —— When an object is put into a container, a pointer to that object is kept in the container.
以上可以翻译为“托管包含”和“非托管包含”。在Qt教材中,又称为组合(composite)和聚合(aggregate),其核心区别就是容器是否管理其元素。
Direct Containment相对比较简单,适合值类型(指针另议),如内置类型(built-in)。
扩展:(参考Qt教材)C++类型可以分为两类,值类型(value)和对象类型(object)。值类型的实例通常相对简单,占用相邻的内存控件,而且可以进行快速复制或者比较。eg: int,char, QString, QDate和QVariant。带有公有默认构造函数、复制构造函数和赋值运算符的任何类都是值类型。相反,对象类型比较复杂,在Qt中,对象类型一般设计为不可复制,如QObject及其子类对象。
Indirect Containment保存的是指针,相对来说能节省内存空间和减少拷贝,对于非内置类型,一般采用这种方式。(注:std::string和QString采用Direct Containment)。
三、Qt指针容器(参考Qt教材)
Indirect Containment实际上实现的就是一种指针容器。指针是一种值类型,单个指针占用内存size_t,能够节省内存空间和提升操作效率(无须直接拷贝object)。此外,通过基类指针,还能利用多态特性。
但是,设计指针容器的时候需要小心设计析构过程,以避免内存泄漏。此外,对指针的访问和维护必须小心控制,以防止解引用空指针或者未定义的指针。规则:
1,当向容器添加指针时,必须确保它已经立即初始化了。如果不方便初始化,则应将其赋值0(NULL,栓住)。
2,当某个指针不再需要时,应移走并删除它。如果不方便立即移走,则被删除的指针应被重新赋值或者设置为0。
3,销毁Qt指针容器时,应调用qDeleteAll()函数,它是针对全部Qt容器的通用算法。也可以调用具体容器的clear()函数。qDeleteAll(container)是一个对容器中的每一个元素调用delete的算法。
四、Ownership of Contained Objects
针对指针容器,由于其存放的元素都是指针,究竟该由容器自动管理其元素指向的内存还是由外部user管理这些内存?
如果由用户管理这些内存,则容器析构时,仅仅只需要将其包含的元素(指针)移出即可。上面所说的Qt指针容器采用的就是这种设计,故“销毁Qt指针容器时,应调用qDeleteAll()函数”。
如果由容器自动管理这些内存,容器销毁时,将逐个对其元素(指针)调用delete。即,在容器的析构函数中,调用qDeleteAll(*this)。前提是,这种类型的容器,其元素(指针)必须指向动态分配的内存空间,而不是静态全局的内存空间或stack空间。
故在此,引入了Ownership(所有权)的概念,让用户来指定指针容器如何管理其元素指向的内存。参考:点击打开链接
从上面容器类的定义也可看出,容器既是一种Object,也有所有权。(多继承)
<span style="font-size:14px;">class OwnerShip { public: void AssertOwnership() { isOwner = true; } void RescindOwnership() { isOwner = false; } bool IsOwner() { return isOwner == true; } protected: OwnerShip() : isOwner(true) {} OwnerShip(OwnerShip & arg) : isOwner(arg.isOwner) { arg.isOwner = true; } private: bool isOwner; };</span>The Ownership class encapsulates a single Boolean variable, isOwner, which records whether the container is the owner of the contained objects.
The behavior of the copy constructor is subtle: It transfers ownership status from the original container to the copy. This behavior is useful because it simplifies the task of returning a container as the result of a function.
每个容器都有一项特性,即容器对它包含的元素的所有权。所有权也可以转移。
这样,就可以像下面的伪代码一样设计容器的函数实现
<span style="font-size:14px;">void SomeContainer::Purge() { if (IsOwner()) for each Object i in this container delete &i; Now clean up the container itself. }</span>这里定义的容器,默认是对其指针元素有Ownership的,而Qt指针容器没有。