在 C++ 中,標準容器(如 std::vector)預設存放的是物件實體(value semantics)。若直接將派生類別物件放入基類別容器,會發生「物件切割」,導致派生類別的特有資料與虛函數行為遺失。本文說明為何會發生切割,並示範如何透過指標(含智慧指標)在容器中實現多態。
Quote),編譯器只分配基類別大小的記憶體。Bulk_quote)以 by-value 方式存入時,只有基類別部分被複製,派生類別擴充的資料成員與虛函數表指標被「切掉」。將容器元素型別改為基類別指標(Quote*)或智慧指標(std::shared_ptr<Quote>),即可保留完整的派生類別物件,並在呼叫虛函數時動態綁定到派生類別的覆寫版本。
/* CppPrimer.cpp
* Created on: 2013.11.12
* Author: Caroline
*/
#include <iostream>
#include <string>
#include <vector>
#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() = 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;
};
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;
}
int main() {
// 直接存放物件:會切割,呼叫基類別虛函數
std::vector<Quote> basket;
basket.push_back(Quote("CppPrimer", 50));
basket.push_back(Bulk_quote("CppPrimer", 50, 10, .25));
std::cout << "Bulk_quote (sliced): "
<< basket.back().net_price(20) << std::endl;
// 使用 shared_ptr:正確多態
std::vector<std::shared_ptr<Quote>> sbasket;
sbasket.push_back(std::make_shared<Quote>("CppPrimer", 50));
sbasket.push_back(std::make_shared<Bulk_quote>("CppPrimer", 50, 10, .25));
std::cout << "Bulk_quote (polymorphic): "
<< sbasket.back()->net_price(20) << std::endl;
return 0;
}
Bulk_quote (sliced): 1000
Bulk_quote (polymorphic): 750
第一行顯示切割後僅呼叫基類別 net_price(50 * 20 = 1000);第二行正確呼叫 Bulk_quote::net_price,享受折扣(50 * 20 * 0.75 = 750)。
在需要多態的容器中,務必使用指標或智慧指標,避免物件切割導致的行為錯誤。現代 C++ 建議優先使用 std::shared_ptr 或 std::unique_ptr 管理物件生命週期,確保資源安全與正確的動態綁定。