条款26操作符函数查找
class X
{
public:
Xoperator %( const X& ) const; //二元取余操作
XmemFunc1( const X&);
voidmemFunc2();
};
可以采用中缀或函数调用语法来调用这个重载的操作符:如下
X a,b,c;
a = b%c; //采用中缀语法调用成员操作符%
a = b.operator %(c); //成员函数调用语法
a = b.memFunc1(c); //另一个成员函数调用
然而,对重重爱操作符中的中缀调用的处理机制与此不同:
X operaator %(const X &, int); //非成员操作符声明
//…
void X::memFunc2()
{
*this% 12; //调用非成员操作符%
operator%(*this, 12); //错误!实参太多
}
第一个对operator %的中缀调用,将会匹配非成员的那一个。这不是一个重载的例子,而是编译器在两个不同的地方查找候选函数。第二个对operator %的非中缀调用准许标准的函数查找规则,因而匹配那个成员函数,但是这里我们会遇到一个错误,因为我们试图将三个实参传递给一个二元函数。(记住,对成员函数的调用存在一个隐式的实参this!)
条款27 能力查询
能力查询通常是通过对不相关的类型进行dynamic_cast转换而表达的。
Class Shape{
};
class Rollable{
};
class Square : public Shape{
};
class Wheel : public Rollable, publicRollable{
};
Shape* s = getSomeShape();
Rollable *roller = dynamic_cast
(s);
这种dynamic_cast用法通常称为很像转型,因为它视图在一个类成次结果中执行横向转换。如果s所指对象时一个Square,那么dynamic_cast将会失败,从而知道s所指向的Shape不是Rollable。如果s指向的是一个Circle或从Rollable派生下来的其他Shape,那么转型将会成功。
条款28 指针比较的含义
在C++中,一个对象可以有多个有效的地址,因此指针比较不是地址问题,而是对象同一性问题。请看以下例子。
#include
class Shape{
public:
int a;
};
class Subject{
public:
int b;
};
class ObservedBlob : public Shape,public Subject
{
};
int main()
{
ObservedBlob* ob = newObservedBlob();
Shape* s = ob;
Subject* subj = ob;
void* v =subj;
printf("%d\n",ob);
printf("%d\n",s);
printf("%d\n",subj);
if(subj ==ob)
printf("subj= ob\n");
else
printf("subj!= ob\n");
if(subj == v)
printf("subj= v\n");
else
printf("subj!= v\n");
if(ob == v )
printf("ob= v\n");
else
printf("ob!= v\n");
return 0;
}
运行结果如下:
3759208
3759208
3759212
subj = ob
subj = v
ob != v
请按任意键继续. . .
由于ob,s,subj都指向同一个对象,因此编译器必须区别ob与s和subj的比较结果均为true。编译器通过将参与比较的指着值之一调整一定的偏移量来完成这种比较。例如,表达式:
ob == subj;
可能被转化翻译为
ob ? (ob+delta ==ubj) : (subj == 0)
所以本例结果为ob =subj。
但一般而言,当我们处理指向对象的指针或引用时,必须小心避免丢失类型信息。指向void的指针是常见的错误。如上,一旦通过将对象的指针复制到void*将会去掉对象中包含的地址类型信息,编译器别无他法,只好求助于原始地址比较。所以才会出现以上结果。
subj = v
ob != v
条款29虚构造函数与Prototype模式
在点餐时,想点一份别人再吃的事物,可用如下程序实现:
class Meal{
public:
virtual~meal();
virtual voideat() = 0;
virtual Meal*clone() const = 0;
//…
};
class Spaghetti : publicMeal{
public:
Spaghetti(const Spaghetti &); //复制构造函数
voideat();
Spaghetti*clone() const
{
return new Spaghetti( *this ); //调用复制构造函数
}
};
constMeal* m = thatGuyMeal(); //不管他正在吃什么
Meal*myMeal = m->clone(); //我都要一份和他一样的!
事实上,我们完全有可能所点的事务没有一点了解。
这里实际上使用了Prototype模式,对一个类型的存在不知情并不会对创建该类型的对象造成任何障碍。
而所谓的虚构造函数,并不是真正的虚构造函数,但是生成对象的一份复制品通常涉及到通过对一个虚函数对其类的构造函数的间接调用,效果上也算得上是虚构造函数了。
条款30 Factory Method模式
Factory Method的本质在于,基类提供一个虚函数挂钩,用于产生适当的产品。每一个派生类可以重写继承的虚函数,为自己产生适当的产品。实际上,我们具备了使用一个位置类型的对象来产生另一个未知类型对象的能力。
条款31协变返回类型
一般来说,一个重写的函数必须与被它重写的函数具有相同的返回类型。
但是,这个规则对于“协变返回类型”不严格。也就说,如果B是一个类类型,并且一个基类虚函数返回B*,那么一个重写的派生类函数可以返回D*,其中的D公有派生于B。如果基类虚函数返回B&,那么一个重写的派生类函数可以返回一个D&。
条款32禁止复制
可以通过将复制操作(复制构造函数和复制赋值函数)声明为private同时不为之提供定义而做到。
条款33制造抽象基类
抽象基类用于目标问题领域的抽象概念,创建这种类型的喜爱那个是没有什么意义的。我们通过至少声明一个纯虚函数使得一个基类成为抽象的,编译器将会确保无人能够创建该抽象基类的任何对象。例如:
class ABC{
public:
virtual ~ABC();
virtual void anOperation() = 0; //纯虚函数
};
然而,有时找不到一个可以成为纯虚函数的合理候选者,但仍希望类的行为像个抽象类。可用如下方法实现。
方法1:可以通过确保类中不存在公有构造函数来模拟抽象基类的性质。这需要我们显式地声明默认构造函数和复制构造函数,并且这两个构造函数应该是受保护的,这是为了既允许派生类的构造函数使用它们,同时阻止创建独立的ABC对象。
class ABC
{
public:
virtual ~ABC();
protect