《C++代码设计与重用》——2.10 练习

简介:

本节书摘来自异步社区出版社《Imperfect C++中文版》一书中的第2章,第2.10节,作者: 【美】Martin D.Carroll , Margaret A.Ellis,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.10 练习

C++代码设计与重用
2.10 练习
2.1 给出下面被建议为最小标准接口函数的反例:

a.输入函数;

b.输出函数;

c.用字符串返回外层类类名的函数。

2.2 考虑类WORM_Pool,它和2.4.1节的Pool类很相似,但这一点除外,它在只能写一次但可读多次的内存区域分配内存块。那么,类WORD_Pool是析构函数的反例吗?请说明是或不是的原因。

2.3 假设我们为用户提供一个类Buf,它描述一个缓冲区:

  class Buf {
  public:
       Buf(size_t sz);
       //...
  };

类Buf的构造函数创建了一个sz个字符大小的缓冲区。假设以后我们的用户想要传递指向Buf的指针,并且他们可以操纵这些指向Buf的指针。这样就有很多指针指向Buf,以至于他们难以决定何时可以安全地删除Buf。

a.为了帮助我们的用户,我们可以创建另外一个类Bufptr,它用来描述一个指向Buf的智能指针:

class Bufptr {
public:
     Bufptr(Buf* p);
     Buf* operator->() {return rep;}
     //...
private:
     Buf* rep;
};

Bufptr的构造函数创建了一个指向p的智能指针,operator->返回这个指针。我们也可以在Bur中增加一个指向Buf的引用计数:

class Buf {
private:
     friend class Bufptr;
     int refcnt;
     //...
};

refcnt的值总等于指向Buf的Bufptr的数目,并且,类Bufptr的各种成员函数会维护refptr的值。当没有其他Bufptr对象指向这个Buf的时候,最后一个Bufptr对象的析构函数会删除它所指向的Buf。那么,如果我们把类Buf的析构函数声明为私有函数,我们应该防止哪些在用户端可能会出现的错误呢?把类Buf的析构函数声明为私有函数的缺点又是什么呢?

b.假设我们没有提供类Buf和类Bufptr,而是提供一个单独的类Bufref,它描述一个指向底层缓冲区的引用:

class Bufref {
     public:
          Bufref(size_t sz);
          Bufref(const Bufref& b);
          //...
     };

它的构造函数将创建一个引用,这个引用指向一块新分配sz个字符的底层缓冲区。拷贝构造函数(具有正规语义)也创建一个新的引用,但它指向b所指向的已经存在的缓冲区。Bufref还提供了一个析构函数,如果已经没有其他的Bufref对象指向这块缓冲区的话,这个析构函数将会删除这块底层缓沖区。请说出类Bufptr相对于类Buf和类Bufptr的优点在什么地方?

c.对于析构函数作为最小标准接口中的函数这个问题,类Buf和类Bufptr是否可以作为令人信服的反例?

2.4 在2.4节里,我们给出了一个类可以作为最小标准接口函数的反例的3个原因,在这个练习里,我们还给出另外两个理论上的但在实际中并不会出现的原因。

a.Complexity_class是一个模拟复杂类型([HU79])的类,假设我们可以创建Complexity_class的实例来模拟复杂类型P和复杂类型NP。那么,针对提议的最小标准接口函数,Complexity_class可以作为哪些函数的反例呢?

b.(*)考虑两个类—TM描述一台图灵机[DW83],Tmset表示图灵机的集合。假设我们可以创建Tmset的实例,用它来表述图灵机的集合,并且这些图灵机在空白磁带开始处将会停止运转。进一步假设存在下面的函数,它将返回tms和{tm}的联合:

Tmset operator+(const Tmset& tms, const TM& tm);

请证明(或说明):Tmset的相等运算符(operator= =)的实现将要求计算一个不可计算的函数(incomputable function)。

2.5 这个练习用于进一步考察浅拷贝和深拷贝。

a.给出一个现实存在的类,它用浅拷贝操作实现它的拷贝构造函数。

b.(*)给出一个现实存在的类,它至少拥有一个指针数据成员,指针的实现不存在内存泄漏,并且通过浅拷贝操作实现它的拷贝构造函数。

c.(**)给出一个现实存在的类,它的浅拷贝操作会破坏某种不变性(invariant,见2.5节),并且这种破坏性是不可以修复的。给出一个现实存在的类,它的深拷贝操作会破坏某种不变性,并且这种破坏性也是不可以修复的。

d.给出一个现实存在的类的实例,它拥有一个强参数,这个强参数用于提供深拷贝和浅拷贝操作。

2.6 在下面的int、double和2.7节中的Rational、Complex4个类型之间,哪些转型是敏感的(sensible):

Rational到int的转型;
int到Rational的转型;
complex到double的转型;
double到Complex的转型。
2.7 C++中的哪些内建的算法转型是敏感的?

2.8 编写一个和C++内建类型int相似的类Int将是相当困难的。

a.编写Int类的接口函数(就是说,给出Int类的声明),切记要提供适当的转型函数。

b.(*)尽管你尽了最大努力,但你设计的类Int的行为和内建int类型的行为有哪些差别?

c.假设你同时需要提供类Char、Short、Long、Float和Double,那么你的Int类需要哪些其他的转型操作呢?Char、Short、Long、Float和Double中的那些类需要具有到Int类的转型操作吗?

2.9 克林法则(Kleen’s theorem,见[DW83])认为一种语言当且仅当它可以表示为正则表达式的时候,才能被有限状态的接受者所接受。

a.(*)假设你的程序库不但提供了一个类FSA(finite state acceptor),用它模拟有限状态的接受者;还提供了另一个类Regex,用Regex来模拟正则表达式。这时,如果要在你的程序库模拟克林法则的话,应该提供什么样的类和函数呢?

b.如果你的解决方法用到了任何隐式转型,并且只有敏感的转型的话,请给出避免多重所有权(multiple ownership)和不必要的转型数目(fanout)的方法。

c.假设你的程序库提供了类Regex,但没有提供类FSA,而你的某些用户使用了一个提供类FSA的程序库;为了使你的用户能够容易地使用a部分所提供的相同功能,应该如何设计你的程序库呢?这个设计的缺点又是什么?

2.10 考虑2.8.3节的Noderef类。

a.当用const来修饰Noderef的时候,如果我们是这样来解释const:既不改变Noderef的值,也不改变底层节点的储存值,那么将会有什么问题发生?

b.我们是否可以这样来设计Noderef:它的接口禁止用户改变底层节点的存储值?如果可以,应该如何设计?

2.11 假设我们希望提供一个函数firstvowel,它返回一个指向给定字符串中的第一个元音字母的指针,这个字符串以null为结束字符;如果这个字符串没有元音字母,则返回0。考虑下列建议的接口:

char* firstvowel(char* s);     //  1
     const char* firstvowel(char* s);    //  2
     char* firstvowel(const char* s);   //  3
     const char* firstvowel(const char* s);  //  4

a.对于(1)到(4)的每个接口,如果我们只给用户提供其中的一个接口,请分别说出会发生什么问题?

b.对于在a部分中发现的问题,请给出你的解决方法?

2.11 参考文献和相关资料

Cargill [Car92]、Cline和Lomow[CL95]、Barton和Nackman[BN94]都讨论了一些关于如何设计好的类的话题。

Liskow和Guttag[LG86]讨论了一些关于抽象状态的概念和其他的一些抽象法则的问题。

正规语义(regular semantics)的术语虽然是很新的,但它所描述的原则已经被好的程序员坚持了很多年了。nice类的第一次使用出现在Lee和Stepanov的[LS93]中,它和本文的nice类在意思上略有差异。

在继承面前正确地实现赋值运算符是相当棘手的,更多信息请参阅C++ Roport杂志中的Meyers文档[Mey94c,Mey94a,Mey94b]。

Meyers[Mey92c]也给出了对最小标准接口建议的批判。

这章里的Pool类是由Koenig在[UNI92]里设计的一个类改编而来的。

Doug Lea建议在2.4.5节里的例子使用垃圾收集机制。

浅拷贝和深拷贝操作导致的问题并不仅仅局限于C++语言,在所有的编程语言都会出现。Knight[Knig93]讨论了这种操作在Smalltalk语言中导致的问题。Gorlen,Orlow和Plexico在[GOP90]中给出了一种在C++程序库实现浅拷贝和深拷贝的技术。

Murray在[Mur88]里杜撰了多重所有权问题(multiple ownership problem)的术语。

要想设计一个可以精确模拟C++真实指针行为的C++智能指针是不可能的,Edelson在[Ede92]解释了这个原因。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

相关文章
|
2月前
|
C++
C++:OJ练习(每日练习系列)
C++:OJ练习(每日练习系列)
34 2
|
7月前
|
C++
关于指针,你不可以错过的练习(c/c++)
关于指针,你不可以错过的练习(c/c++)
47 0
|
8月前
|
C++
C++练习:设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系。 顺便熟悉一下分文件编写
C++练习:设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系。 顺便熟悉一下分文件编写
71 0
|
2月前
|
Shell C++
C++:OJ练习(每日练习系列)
C++:OJ练习(每日练习系列)
26 1
|
2月前
|
Serverless C++
C++:OJ练习(每日练习!)
C++:OJ练习(每日练习!)
33 0
|
3月前
|
Java C++ Python
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-456 求链表各节点的平均值(C++解法)
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-456 求链表各节点的平均值(C++解法)
29 0
|
3月前
|
Java C++ Python
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-161 Abbott’s Revenge(C++写法)
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-161 Abbott’s Revenge(C++写法)
125 42
|
9月前
|
C++
【C/C++练习】合并k个已排序的链表(一)
【C/C++练习】合并k个已排序的链表(一)
48 0
|
4月前
|
存储 缓存 算法
《C++ Concurrencyin Action》第8章--并发代码设计
《C++ Concurrencyin Action》第8章--并发代码设计
|
9月前
|
C++
C++ Primer Plus 第二章编程练习
C++ Primer Plus 第二章编程练习