玩命加载中 . . .

15.7-容器与继承


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,然后返回一个新分配对象的指针,用来给智能指针绑定,这样智能指针就知道它所绑定的对象的大小


文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录