C++ Primer 学习笔记_71_面向对象编程 --句柄类与继承
2014-05-15 08:20
726 查看
面向对象编程
--句柄类与继承
引言:C++中面向对象编程的一个颇具讽刺意味的地方是:不能使用对象支持面向对象编程,相反,必须使用指针或引用。
[cpp] view
plaincopy
void get_prices(Item_base object,
Item_base *pointer,
Item_base &reference)
{
//需要根据指针或引用实际所绑定的类型进行调用
cout << pointer -> net_price(1) << endl;
cout << reference.net_price(1) << endl;
//一直调用基类Item_base版本中的 net_price
cout << object.net_price(1) << endl;
}
通过pointer和reference进行的调用在运行时根据它们所绑定对象的动态类型而确定。但是使用指针或引用会加重类用户的负担,像上一节的习题15.29:需要用户显式释放动态申请的内存。
C++解决该问题的一个通用的技术是定义包装类或句柄类。句柄类存储和管理基类指针,指针所指向对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类型对象。用户通过句柄类访问继承层次的操作。因为句柄类使用指针执行操作,虚成员的行为将在运行时根据句柄实际绑定的对象的类型而变化。因此,句柄的用户可以获得动态行为但无须操心指针的管理。
包装了继承层次的句柄有两个重要的设计考虑因素:
1)像对任何保存指针的类一样,必须确定对复制控制做些什么。包装了继承层次的句柄通常表现得像一个智能指针,或者像一个值。
2)句柄类决定句柄接口屏蔽还是不屏蔽继承层次,如果不屏蔽继承层次,用于必须了解和使用基本层次中的对象。
一、指针型句柄
我们将定义一个名为Sales_item的指针型句柄类,表示Item_base层次。Sales_item的用户将像一个指针一样使用它:用户将Sales_item绑定到Item_base类型的对象并使用*和->操作符进行Item_base的操作:
[cpp] view
plaincopy
//将一个句柄对象绑定到Bulk_item对象
Sales_item item(Bulk_item("0-201-82480-1",35,3,0.20));
//调用Bulk_item类的net_price函数
item -> net_price();
但是,用户不必管理句柄指向的对象,Sales_item类将完成这部分工作。当用户通过Sales_item类对象调用函数时,将获得多态行为。
1、定义句柄
Sales_item类有三个构造函数:默认构造函数、复制构造函数和接受Item_base对象的构造函数。第三个构造函数将复制 Item_base 对象,并保证: 只要 Sales_item 对象存在副本就存在。当复制
Sales_item 对象或给 Sales_item 对象赋值时,将复制指针而不是复制对象。像对其他指针型句柄类一样,将用使用计数来管理副本。
迄今为止,我们已经使用过的使用计数式类,都使用一个伙伴类来存储指针 和相关的使用计数。这个例子将使用不同的设计,如图所示。
Sales_item 类将有两个数据成员,都是指针:一个指针将指向 Item_base 对象,而另一个 将指向使用计数。Item_base 指针可以指向 Item_base 对象也可以指向Item_base 派生类型的对象。通过指向使用计数,多个 Sales_item 对象可以共享同一计数器。
除了管理使用计数之外,Sales_item类还定义解引用操作符和箭头操作符:
[cpp] view
plaincopy
class Sales_item
{
public:
Sales_item():p(0),use(new size_t(1)){}
Sales_item(const Item_base &);
Sales_item(const Sales_item &rhs):p(rhs.p),use(rhs.use)
{
++ *use;
}
~Sales_item()
{
decr_use();
}
Sales_item &operator=(const Sales_item &);
const Item_base *operator->() const
{
if (p)
{
return p;
}
else
{
throw std::logic_error("unbound Sales_item");
}
}
const Item_base &operator*() const
{
if (p)
{
return *p;
}
else
{
throw std::logic_error("unbound Sales_item");
}
}
private:
Item_base *p;
std::size_t *use;
void decr_use()
{
if (-- *use == 0)
{
delete p;
delete use;
}
}
};
2、使用计数式复制控制
复制Sales_item对象包括复制两个指针和将使用计数加1。析构函数将使用计数减1,如果计数减至0就撤销指针。因为赋值操作符需要完成同样的工作,所以在一个名为decr_use的私有实用函数中实现析构函数的行为。
赋值操作符:
[cpp] view
plaincopy
Sales_item &Sales_item::operator=(const Sales_item &rhs)
{
++ * rhs.use;
decr_use();
p = rhs.p;
use = rhs.use;
return *this;
}
除了复制控制成员以外,Sales_item定义的其他函数是操作函数operator*和operator->,用户将通过这些操作符访问Item_base成员。因为这两个操作符分别返回指针和引用,所以通过这些操作符调用的函数将进行动态绑定。
我们只定义了这些操作符的const版本,因为基础Item_base层次中的成员都是 const成员。
3、构造函数
出了复制构造函数,我们的句柄有两个构造函数:
1)将Item_base指针设置为0以指出该句柄并未关联任何对象。构造函数在自由存储区申请一个新的计数器并将它初始化为1。
2)我们希望句柄的用户创建自己的对象,并在这些对象上关联句柄。构造函数将分配适当类型的新对象并将形参复制到新分配的对象中,这样,Sales_item类将拥有对象并能够保证在关联到该对象的最后一个Sales_item对象消失之前不会删除对象。
二、复制未知类型
要实现接受Item_base对象的构造函数,必须首先解决一个问题:我们不知道给予构造函数的对象的实际类型。我们不知道它是一个Item_base对象或者是一个Item_base派生类型的对象。句柄类经常需要在不知道对象的确切类型时分配书籍的新副本。
【提示】
解决该问题的通用方法是定义虚操作进行复制,我们将该操作命名为clone。
为了支持句柄类,需要从基类开始,在继承层次的每个类型中增加clone,基类必须将该函数定义为虚函数:
[cpp] view
plaincopy
class Item_base
{
public:
virtual Item_base *clone() const
{
return new Item_base(*this);
}
};
每个类必须重定义该虚函数。因为函数的存在是为了生成类对象的新副本,所以定义返回类型为类本身:
[cpp] view
plaincopy
class Bulk_item : public Item_base
{
public:
/**如果虚函数的基类实例返回类类型的引用或指针,
*则该虚函数的派生类实例可以返回基类实例返回的类型的派生类
*(或者是类类型的指针或引用)。
*/
virtual Bulk_item *clone() const
{
return new Bulk_item(*this);
}
};
定义句柄构造函数
[cpp] view
plaincopy
Sales_item::Sales_item(const Item_base &rhs):
p(rhs.clone()),use(new size_t(1)) {}
它调用形参的clone产生那个对象的(虚)副本:如果实参是Item_base对象,则运行Item_base的clone函数;如果实参是Bulk_item对象,则执行Bulk_item的clone函数。
[cpp] view
plaincopy
//P508 习题15.31
class Lds_item : public Item_base
{
public:
virtual Lds_item *clone() const
{
return new Lds_item(*this);
}
//...As before
};
[cpp] view
plaincopy
//习题15.32/34
//(a)
class Item_base
{
public:
Item_base(const std::string &book = "",
double sales_price = 0.0):
isbn(book),price(sales_price) {}
virtual void debug(bool ctrl = 1,ostream &os = cout) const
{
if (!ctrl)
return;
os << "ISBN:\t" << isbn << endl
<< "price:\t" << price << endl;
}
private:
std::string isbn;
protected:
double price;
};
class Disc_item : public Item_base
{
public:
Disc_item(const std::string &book = "",
double sales_price = 0.0,
std::size_t qty = 0,
double disc_rate = 0.0):
Item_base(book,sales_price),quantity(qty),discount(disc_rate) {}
virtual void debug(bool ctrl = 1,ostream &os = cout) const
{
if (!ctrl)
return ;
Item_base::debug(os);
os << "quantity:\t" << quantity << endl
<< "discount:\t" << discount << endl;
}
protected:
std::size_t quantity; //可实行折扣的数量
double discount; //折扣率
};
[cpp] view
plaincopy
//(b)
class Item_base
{
public:
Item_base(const std::string &book = "",
double sales_price = 0.0,
bool dbg = false):
isbn(book),price(sales_price),is_debug(dbg) {}
virtual void debug(ostream &os = cout) const
{
if (!is_debug)
return;
os << "ISBN:\t" << isbn << endl
<< "price:\t" << price << endl;
}
void set_debug(bool dbg)
{
is_debug = dbg;
}
private:
std::string isbn;
protected:
double price;
bool is_debug;
};
class Disc_item : public Item_base
{
public:
Disc_item(const std::string &book = "",
double sales_price = 0.0,
std::size_t qty = 0,
double disc_rate = 0.0,
bool dbg = false):
Item_base(book,sales_price,dbg),quantity(qty),discount(disc_rate) {}
virtual void debug(ostream &os = cout) const
{
if (!is_debug)
return ;
Item_base::debug(os);
os << "quantity:\t" << quantity << endl
<< "discount:\t" << discount << endl;
}
protected:
std::size_t quantity; //可实行折扣的数量
double discount; //折扣率
};
class Bulk_item : public Disc_item
{
public:
Bulk_item(const std::string &book = "",
double sales_price = 0.0,
std::size_t qty = 0,
double disc_rate = 0.0,
bool dbg = false):
Disc_item(book,sales_price,qty,disc_rate,dbg) {}
};
class Lds_item : public Disc_item
{
public:
Lds_item(const std::string &book = "",
double sales_price = 0.0,
std::size_t qty = 0,
double disc_rate = 0.0,
bool dbg = false):
Disc_item(book,sales_price,qty,disc_rate,dbg) {}
};
相关文章推荐
- C++ Primer 学习笔记_71_面向对象编程 --句柄类与继承
- C++ Primer 学习笔记_72_面向对象编程 -句柄类与继承[续]
- C++ Primer 学习笔记_72_面向对象编程 --句柄类与继承[续]
- C++ Primer 学习笔记_67_面向对象编程 -变换与继承、复制控制与继承
- C++ Primer 学习笔记_67_面向对象编程 --转换与继承、复制控制与继承
- C++ Primer 学习笔记_69_面向对象编程 -继承景况下的类作用域
- c++ primer学习之-----面向对象编程(继承与动态绑定)
- C++ Primer 学习笔记_70_面向对象编程 -纯虚函数、器皿与继承
- C++ primer第二次阅读学习笔记(第15章:面向对象编程)
- C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类
- c#学习笔记五 面向对象编程的基本概念 接口 继承和多态
- C++ Primer 学习笔记_73_面向对象编程 -再谈文本查询示范
- C++ Primer 学习笔记_66_面向对象编程 -定义基类跟派生类[续]
- C++ Primer(第4版)-第4部分:面向对象编程与泛型编程——学习笔记
- C++ Primer 学习笔记十五 —— 面向对象编程
- (原创)c#学习笔记08--面向对象编程简介02--OOP技术02--继承
- C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域
- C++ Primer学习笔记 — 复制控制与继承
- c++ primer 学习笔记25 面向对象编程
- javascript 学习笔记之面向对象编程(二):继承&多态