c++ primer笔记7

1. Containers and Inheritance

We’d like to use containers (or built-in arrays) to hold objects that are related by inheritance. However, the fact that objects are not polymorphic affects how we can use containers with types in an inheritance hierarchy. Because derived objects are “sliced down” when assigned to a base object, containers and types related by inheritance do not mix well.

The only viable alternative would be to use the container to hold pointers to our objects. This strategy works, but at the cost of pushing onto our users the problem of managing the objects and pointers. The user must ensure that the objects pointed to stay around for as long as the container. If the objects are dynamically allocated, then the user must ensure that they are properly freed when the container goes away.

2. Handle Classes and Inheritance

One of the ironies of object-oriented programming in C++ is that we cannot use objects to support polymorphism. Instead, we must use pointers and references, not objects. Unfortunately, using pointers or references puts a burden on the users of our classes.

A common technique in C++ is to define a so-called handle class . The handle class stores and manages a pointer to the base class. The type of the object to which that pointer points will vary; it can point at either a base- or a derived-type object. Users access the operations of the inheritance hierarchy through the handle. Because the handle uses its pointer to execute those operations, the behavior of virtual members will vary at run time depending on the kind of object to which the handle is actually bound. Users of the handle thus obtain dynamic behavior but do not themselves have to worry about managing the pointer.

3. A Pointerlike Handle

We’ll define a pointerlike handle class, named Sales_item , to represent our Item_base hierarchy. Users of Sales_item will use it as if it were a pointer: Users will bind a Sales_item to an object of type Item_base and will then use the * and -> operations to execute Item_base operations:

// bind a handle to a Bulk_item object
Sales_item item(Bulk_item("0-201-82470-1", 35, 3, .20));
item->net_price(); // virtual call to net_price function

However, users won’t have to manage the object to which the handle points; the Sales_item class will do that part of the job. When users call a function through a Sales_item , they’ll get polymorphic behavior.

The use-counted classes we’ve used so far have used a companion class to store the pointer and associated use count. In this class, we’ll use a different design. The Sales_item class will have two data members, both of which are pointers: One pointer will point to the Item_base object and the other will point to the use count. The Item_base pointer might point to an Item_base object or an object of a type derived from Item_base.

class Sales_item {
public:
// default constructor: unbound handle
Sales_item(): p(0), use(new std::size_t(1)) { }
// attaches a handle to a copy of the Item_base object
Sales_item(const Item_base&);
// copy control members to manage the use count and pointers
Sales_item(const Sales_item &i):
p(i.p), use(i.use) { ++*use; }
~Sales_item() { decr_use(); }
Sales_item& operator=(const Sales_item&);
// member access operators
const Item_base *operator->() const { if (p) return p;
else throw std::logic_error("unbound Sales_item"); }
const Item_base &operator*() const { if (p) return *p;
else throw std::logic_error("unbound Sales_item"); }
private:
Item_base *p; // pointer to shared item
std::size_t *use; // pointer to shared use count
// called by both destructor and assignment operator to free pointers
void decr_use()
{ if (--*use == 0) { delete p; delete use; } }

};

Sales_item&
Sales_item::operator=(const Sales_item &rhs)
{
++*rhs.use;
decr_use();
p = rhs.p;
use = rhs.use;
return *this;
}

Sales_item(const Item_base&);The constructor will allocate a new object of the appropriate type and copy the parameter into that newly allocated object. That way the Sales_item class will own the object and can guarantee that the object is not deleted until the last Sales_item attached to the object goes away. To implement the constructor that takes an Item_base , we must first solve a problem: We do not know the actual type of the object that the constructor is given. We know that it is an Item_base or an object of a type derived from Item_base. The common approach to solving this problem is to define a virtual operation to do the copy, which we’ll name clone.

class Item_base {
public:
virtual Item_base* clone() const
{ return new Item_base(*this); }
};
class Bulk_item : public Item_base {
public:
Bulk_item* clone() const
{ return new Bulk_item(*this); }
};

Sales_item::Sales_item(const Item_base &item):
p(item.clone()), use(new std::size_t(1)) { }

If the base instance of a virtual function returns a reference or pointer to a class type, the derived version of the virtual may return a class publicly derived from the class returned by the base class instance (or a pointer or a reference to a class type).