當以 by-value 方式將派生類別物件放入基類別容器(如 std::vector<Quote>),會發生物件切割,僅保留基類別部分,虛函數失去多態行為。
在基類別定義虛擬 clone() 函式,回傳 Quote*(或智慧指標)。派生類別覆寫 clone() 回傳自身型別指標。容器存放 std::shared_ptr<Quote>,呼叫 clone() 即可獲得正確的派生類別副本。
容器型別改為 std::vector<std::shared_ptr<Quote>>,以 std::make_shared<Derived>() 建立物件,自動實現動態綁定,無需手動實作 clone()。
/* CppPrimer.cpp
* Created on: 2013.11.12
* Author: Caroline
*/
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <utility>
#include <memory>
#include <cstddef>
using namespace std;
class Quote {
public:
Quote() = default;
Quote(const std::string& book, double sales_price)
: bookNo(book), price(sales_price) {}
std::string isbn() const { return bookNo; }
virtual double net_price(std::size_t n) const { return n * price; }
virtual Quote* clone() const & { return new Quote(*this); }
virtual Quote* clone() && { return new Quote(std::move(*this)); }
virtual ~Quote() = default;
private:
std::string bookNo;
protected:
double price = 0.0;
};
class Disc_quote : public Quote {
public:
Disc_quote() = default;
Disc_quote(const std::string& book, double price,
std::size_t qty, double disc)
: Quote(book, price), quantity(qty), discount(disc) {}
double net_price(std::size_t) const = 0;
protected:
std::size_t quantity = 0;
double discount = 0.0;
};
class Bulk_quote final : public Disc_quote {
public:
Bulk_quote() = default;
Bulk_quote(const std::string& book, double p,
std::size_t qty, double disc)
: Disc_quote(book, p, qty, disc) {}
double net_price(std::size_t cnt) const override;
virtual Bulk_quote* clone() const & { return new Bulk_quote(*this); }
virtual Bulk_quote* clone() && { return new Bulk_quote(std::move(*this)); }
};
double Bulk_quote::net_price(std::size_t cnt) const {
if (cnt >= quantity) return cnt * (1 - discount) * price;
else return cnt * price;
}
double print_total(std::ostream& os, const Quote& item, std::size_t n) {
double ret = item.net_price(n);
os << "ISBN: " << item.isbn()
<< " # sold: " << n
<< " total due: " << ret << std::endl;
return ret;
}
class Basket {
public:
void add_item(const std::shared_ptr<Quote>& sale) { items.insert(sale); }
void add_item(const Quote& sale) {
items.insert(std::shared_ptr<Quote>(sale.clone()));
}
void add_item(Quote&& sale) {
items.insert(std::shared_ptr<Quote>(std::move(sale).clone()));
}
double total_reciept(std::ostream&) const;
private:
static bool compare(const std::shared_ptr<Quote>& lhs,
const std::shared_ptr<Quote>& rhs) {
return lhs->isbn() < rhs->isbn();
}
std::multiset<std::shared_ptr<Quote>, decltype(compare)*> items{compare};
};
double Basket::total_reciept(std::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 << std::endl;
return sum;
}
int main() {
Basket bsk;
bsk.add_item(Quote("CppPrimer", 45));
bsk.add_item(Bulk_quote("EffectiveCpp", 50, 2, 0.15));
bsk.add_item(Bulk_quote("EffectiveCpp", 50, 2, 0.15));
bsk.add_item(Bulk_quote("EffectiveCpp", 50, 2, 0.15));
bsk.total_reciept(std::cout);
return 0;
}
ISBN: CppPrimer # sold: 1 total due: 45
ISBN: EffectiveCpp # sold: 3 total due: 127.5
Total Sale: 172.5
虛擬複製函式配合智慧指標,能在容器中正確保存派生類別物件,實現多態。現代 C++ 更推薦直接使用 std::shared_ptr 存放,簡化設計並確保資源管理安全。