15.7 容器与继承
如果声明一个存放Quote
类型的数组,放Quote
对象没问题,如果放进去一个Bulk_quote
对象,看似没问题,实际上只是放入了Bulk_quote
对象Quote
部分
vector<Quote> basket;
basket.push_back(Quote("0-201-1", 50));
basket.push_back(Bulk_quote("0-201-8", 10, .25));
cout << basket.back().net_price(15) << endl;
因为这里只是把Quote
那部分放入数组,所以调用虚函数net_price
的时候,调用的是Quote
定义的版本,输出15×50
,而不是Bulk_quote
的版本15×50×0.75
当派生类对象被赋值给基类对象时,其中的派生类部分将被“切掉”,因此容器和存在继承关系的类型无法兼容
在容器中放置(智能)指针而非对象
正确的做法是在容器中放置指针,而非对象
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("0-201-1", 50));
basket.push_back(make_shared<Bulk_quote>("0-201-8", 10, .25));
cout << basket.back()->net_price(15) << endl;
因为存放的是基类的指针,调用虚函数时执行动态绑定,就可以调用Bulk_quote
版本
class Basket {
public:
void add_item(const shared_ptr<Quote>& sale) {
items.insert(sale);
}
double total_receipt(ostream&) const;
private:
// 该函数用于比较shared_ptr,multiset成员会用到它
static bool compare(const shared_ptr<Quote>& lhs, const shared_ptr<Quote>& rhs) {
return lhs.isbn() < rhs.isbn();
}
multiset<shared_ptr<Quote>, decltype(compare)> items{compare};
};
如果直接使用容器不能满足要求,可以用自定义类,类里面有个成员是容器,用来保存数据,并且可以重新定义接口
因为shared_ptr
没有定义小于运算符,所以为了对元素排序我们必须提供自己的比较运算符,所以类的定义中multiset
将使用一个与compare
成员类型相同的函数来对其中的元素进行排序
double Basket::total_receipt(ostream& os) const {
double sum = 0.0;
for (auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter)) {
sum += print_total(os, **iter, items.count(*iter));
}
os << "Total sales: " << sum << endl;
return sum;
}
iter
指向ISBN相同的的一批元素中的第一个,upper_bound
返回一个迭代器,该迭代器指向所有与iter
相等的元素中最后一个元素的下一位置,因此,我们得到的迭代器或者指向集合的末尾,或者指向下一本书籍
解引用iter
,也就是*iter
,可以得到一个指向准备打印对象的shared_ptr
,再对智能指针解引用就得到Quote
对象或其派生类对象,调用print_total
来计算每本书的总价格,其内部会调用虚函数net_price
,传递的参数就是每本书的售出数量,根据指针所指向对象的动态类型来确定总价格计算方式
隐藏指针
现在的add_item
只能接收智能指针参数,如果我们想要接收对象,可以重载该函数
void add_item(const Quote& sale); // 拷贝给定的对象
void add_item(Quote&& sale); // 移动给定的对象
但是这里的Quote
引用或右值引用并不知道它所绑定的对象的实际类型,插入到集合中时,需要调用new
来分配内存,但我们并不知道对象的实际类型,它可能是Quote
,也可能是Bulk_quote
,如果分配的时候是new Quote(sale)
,就会丢失掉派生类部分的数据
模拟虚拷贝
为了解决上述问题,我们给Quote
定义一个虚函数,该虚函数会申请一份当前的对象的拷贝
class Quote {
public:
// 左值版本
virtual Quote* clone() const & {
return new Quote(*this);
}
// 右值版本
virtual Quote* clone() && {
return new Quote(std::move(*this));
}
};
class Bulk_quote : public Quote {
Bulk_quote* clone() const & {
return new Bulk_quote(*this);
}
Bulk_quote* clone() && {
return new Bulk_quote(std::move(*this));
}
};
根据不同类型的参数调用左值和右值版本的add_item
class Basket {
public:
void add_item(const Quote& sale) {
items.insert(shared_ptr<Quote>(sale.clone()));
}
void add_item(Quote&& sale) {
items.insert(shared_ptr<Quote>(std::move(sale).clone()));
}
};
sale
的动态类型决定调用Quote
版本的clone
还是Bulk_quote
版本的clone
,然后返回一个新分配对象的指针,用来给智能指针绑定,这样智能指针就知道它所绑定的对象的大小