条款39:避免在继承体系中做向下转型(downcast)动作

1,先看个例子:
class Person { ... };
class BankAccount
{
public:
BankAccount(const Person *primaryOwner,
const Person *jointOwner);
virtual ~BankAccount();
virtual void makeDeposit(double amount) = 0;
virtual void makeWithdrawal(double amount) = 0;
virtual double balance() const = 0;
...
};

class SavingsAccount: public BankAccount
{
public:
SavingsAccount(const Person *primaryOwner,
const Person *jointOwner);
~SavingsAccount();
void creditInterest(); // add interest to account
...
};

使用:
list<BankAccount*> allAccounts;
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
(*p)->creditInterest(); // error!
}
错误:creditInterest在BankAccounts中不存在.

可行的纠正:
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
static_cast<SavingsAccount*>(*p)->creditInterest(); //downcast
}

注:转型(cast)之于程序员,犹如苹果之于夏娃.

2,如果又有一个新的账户加入.
class CheckingAccount: public BankAccount
{
public:
void creditInterest(); // add interest to account
...
};

我们这时候必须这么做:
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
if (*p points to a SavingsAccount)
static_cast<SavingsAccount*>(*p)->creditInterest();
else
static_cast<CheckingAccount*>(*p)->creditInterest();
}

根据型别做事,这不是C++的精神,应该使用虚拟函数.
class BankAccount { ... }; // as above

// new class representing accounts that bear interest
class InterestBearingAccount: public BankAccount
{
public:
virtual void creditInterest() = 0; //提供接口
...
};
class SavingsAccount: public InterestBearingAccount
{
... // as above
};
class CheckingAccount: public InterestBearingAccount
{
... // as above
};

如下图所示:

[img]http://dl.iteye.com/upload/attachment/257950/636d052c-72b7-3f3d-af84-ef63c639a52e.png[/img]

这时候,你可以这么使用:
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
static_cast<InterestBearingAccount*>(*p)->creditInterest();
}

3,还有另外一种解决的办法:
class BankAccount
{
public:
virtual void creditInterest() {} //基类提供一个什么都不做的缺省实现
...
};
class SavingsAccount: public BankAccount { ... };
class CheckingAccount: public BankAccount { ... };

list<BankAccount*> allAccounts;// look ma, no cast!

for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
(*p)->creditInterest();
}

4,总结:为了摆脱downcast,不论花多少努力都是值得的.
但是偶尔还是无法摆脱downcast.
例如:当我们无权改变BankAccount, SavingsAccount, 或 allAccounts的定义.
这时候,相对于static_cast,还有个比较好的办法:"safe downcasting"(安全向下转型)
使用dynamic_cast,如果失败,会传回null指针.

回到一开始的地方:

class Person { ... };
class BankAccount
{
public:
BankAccount(const Person *primaryOwner,
const Person *jointOwner);
virtual ~BankAccount();
virtual void makeDeposit(double amount) = 0;
virtual void makeWithdrawal(double amount) = 0;
virtual double balance() const = 0;
...
};

class SavingsAccount: public BankAccount
{
public:
void creditInterest(); // add interest to account
...
};

class CheckingAccount: public BankAccount
{
public:
void creditInterest(); // add interest to account
...
};

使用:
list<BankAccount*> allAccounts;

void error(const string& msg);

//至少可以保证downcast失败时,可以侦测到.
for (list<BankAccount*>::iterator p = allAccounts.begin(); p != allAccounts.end(); ++p)
{
if (SavingsAccount *psa = dynamic_cast<SavingsAccount*>(*p))
{
psa->creditInterest();
}
else if (CheckingAccount *pca = dynamic_cast<CheckingAccount*>(*p))
{
pca->creditInterest();
}
// uh oh — unknown account type
else
{
error("Unknown account type!");
}
}

注:downcast必然导致if-then-else风格,比起虚函数,拙劣之极.
万不得已,不要这么使用.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值