- 如果基类的析构函数不是虚函数,则delete一个指向派生类对象的基类指针将产生未定义的形为。
class Quote
{
public:
virtual ~Quote() = default; //动态绑定析构函数
};
Quote *itemp = new Quote; //静态类型与动态类型一致
delete itemp; //调用Quote的析构函数
itemp = new Bulk_quote; //静态类型与动态类型不一致
delete itemp; //调用Bluk_quote的析构函数
#Note#
此前说如果一个类需要析构函数,一般也哦同意需要拷贝和赋值操作。
基类的析构函数不遵循该规则。一个基类总是需要析构函数,并且能将其定义为虚函数。
- 因为基类缺少移动操作会阻止派生类拥有自己的合成移动操作,所以当我们确实需要执行移动操作时,应该首先在基类中定义。
- 如果构造函数和析构函数调用了某个虚函数,则我们应该执行与构造函数或析构函数所属类型对象的。
- 当派生类对象被赋值给基类对象时,其中的派生类部分将被切掉,因此容器和存在继承关系的类型无法兼容。于是更好的方法是,在容器中放置(智能)指针而非对象。
vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("0-201-82470-1", 50));
basket.push_back(make_shared<Bulk_quote>("0-201-82470-1", 50, 10, 0.25));
此时basket存着shared_ptr,所以必须解引用basket.back()的返回值以获得运行net_price的对象
cout << basket.back()->net_price(15) << endl;
- 编写Basket类。对于c++面向对象编程来说,一个悖论是我们无法直接使用对象进行面向对象编程。我们必须使用指针和引用。因为指针会增加程序的复杂性,所以我们经常定义一些辅助的类来处理这种复杂情况。
class Basket
{
public:
//Basket使用合成的默认构造函数和拷贝控制成员
void add_item(const std::shared_ptr<Quote> &sale)
{
items.insert(sale);
}
//打印每本书的总价和购物篮中的所有书的总价
double total_receipt(std::ostream&) const;
private:
//该函数用于比较shared_ptr,multiset成员会用到它
static bool compare(const std::shared_ptr<Quote> &lhs, const std::shared_ptr<Quote> &rhs)
{
reutrn lhs->isbn() < rhs->isbn();
}
//multiset保存多个报价,按照compare成员排序
std::multiset<std::shared_ptr<Quote>, decltype(compare)*> items(compare);
};
最后一个声明,它是定义了一个指向Quote对象的shared_ptr的multiset。这个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 Sale: " << sum << endl;
return sum;
}
调用upper_bound函数可以跳过与当前关键字相同的所有元素。返回的迭代器指向最后一个相同元素的下一个位置。
解引用iter后得到一个指向将要打印的对象的shared_ptr,再解引用得到一个Quote对象(或者Quote派生类对象)
print_total调用的哪个虚函数net_price依赖于**iter类型