常见错误90:非适当的容器类型值可替换性
对C++软件工程师而言,STL容器组件应该是容器应用的首先。不过,STL容器组件并不能满足
所有的需求,因为它们为了发挥出强大的功能,不得不设置一定的限制。有关STL容器组件
的一个有意思的事实是:由于它们都是以模板实现的,其结构和行为诸多方面都是编译时就决定
了的。这种特点带来了短小精悍的实现,在编译时精确地转化成恰好合用的静态执行码。
但是,并不一定在编译时就能准备好所有的有关信息。
在设计中使用平行继承谱系通常来讲是有问题的,因为对其中一个继承谱系的修改会要求
其他的继承谱系作出对应的变化。我们希望能够将代码更改集中一处。
工厂方式设计模式为抽象基类接口的用户提供了一种机制,使他们能够生成适当派生类对象,
同时又能对其具体类型保持不知情。
通常,工厂方法设计模式是病态的基于类型分派的代码的替代方案。
有一个不幸但又是普遍存在的趋势,就是看到容量持有元素类型的可替代性就想当然地
认为持有这些元素的容器类型亦有可替换性。可替换性在数组这个C++语言内建的容器上
是没有的。持有派生类对象的数组对于持有基类对象的数组并无可替换性。警示着用户
自定义的持有可替换类型元素的容器类型不能如此行事。
容器类型之间的可替代性,如果需要的话,那么设计时应该关注其本身的结构,而非其
对C++软件工程师而言,STL容器组件应该是容器应用的首先。不过,STL容器组件并不能满足
所有的需求,因为它们为了发挥出强大的功能,不得不设置一定的限制。有关STL容器组件
的一个有意思的事实是:由于它们都是以模板实现的,其结构和行为诸多方面都是编译时就决定
了的。这种特点带来了短小精悍的实现,在编译时精确地转化成恰好合用的静态执行码。
但是,并不一定在编译时就能准备好所有的有关信息。
在设计中使用平行继承谱系通常来讲是有问题的,因为对其中一个继承谱系的修改会要求
其他的继承谱系作出对应的变化。我们希望能够将代码更改集中一处。
工厂方式设计模式为抽象基类接口的用户提供了一种机制,使他们能够生成适当派生类对象,
同时又能对其具体类型保持不知情。
通常,工厂方法设计模式是病态的基于类型分派的代码的替代方案。
有一个不幸但又是普遍存在的趋势,就是看到容量持有元素类型的可替代性就想当然地
认为持有这些元素的容器类型亦有可替换性。可替换性在数组这个C++语言内建的容器上
是没有的。持有派生类对象的数组对于持有基类对象的数组并无可替换性。警示着用户
自定义的持有可替换类型元素的容器类型不能如此行事。
容器类型之间的可替代性,如果需要的话,那么设计时应该关注其本身的结构,而非其
持有元素类型的结构。
bondlist.h
#ifndef BONDLIST_H
#define BONDLIST_H
class Object
{ public: virtual ~Object(){} };
class Instrument : public Object
{ public: virtual double pv() const = 0; };
class Bond : public Instrument
{ public: double pv() const; };
class ObjectList {
public:
ObjectList() : c(0) {}
void insert( Object *o ) { a[c++] = o; }
Object *get();
size_t size() const { return c; }
Object *operator [](int i) { return a[i]; }
//...
private:
int c;
Object *a[100];
};
class BondList : public ObjectList { // bad idea!!!
public:
void insert( Bond *b )
{ ObjectList::insert( b ); }
Bond *get()
{ return static_cast<Bond *>(ObjectList::get()); }
Bond *operator []( int a ) { return (Bond *)ObjectList::operator [](a); }
//...
};
#endif
bondlist.cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include "bondlist.h"
double bondPortfolioPV( BondList &bonds ) {
double sumpv = 0.0;
for( int i = 0; i < bonds.size(); ++i ) {
Bond *b = bonds[i];
sumpv += b->pv();
}
return sumpv;
}
class UnderpaidMinion : public Object {
public:
virtual double pay()
{ std::cout << "UnderpaidMinion::pay!" << std::endl; return 0.0; }
};
void sneaky( ObjectList &list )
{ list.insert( new UnderpaidMinion ); }
BondList bondList;
BondList &getBondList() {
return bondList;
}
void victimize() {
BondList &blist = getBondList();
sneaky( blist );
bondPortfolioPV( blist ); //done!
}
container.h
#ifndef CONTAINER_H
#define CONTAINER_H
template <typename T>
class Iter;
template <typename T>
class Container {
public:
virtual ~Container() {}
virtual Iter<T> *genIter() const = 0; // Factory Method
virtual Container *clone() const = 0; // Prototype
virtual void insert( const T & ) = 0;
};
template <typename T>
class Iter {
public:
virtual ~Iter() {}
virtual void reset() = 0;
virtual void next() = 0;
virtual bool done() const = 0;
virtual T &get() const = 0;
};
#endif
list.h
#ifndef LIST_H
#define LIST_H
#include <memory>
template <typename T>
class ListIter;
template <typename T>
struct ListNode {
ListNode( ListNode *n, const T &t )
: n_( n ), e_( t ) {}
ListNode *n_;
T e_;
};
template <typename T>
class List : public Container<T> {
public:
List()
: hd_( 0 ) {}
~List();
ListIter<T> *genIter() const
{ return new ListIter<T>( *this ); }
List *clone() const;
void insert( const T &t )
{ ListNode<T> *tmp = new ListNode<T>( hd_, t ); hd_ = tmp; }
private:
friend class ListIter<T>;
List( const List & );
List &operator =( const List & );
ListNode<T> *hd_;
};
template <typename T>
class ListIter : public Iter<T> {
public:
ListIter( const List<T> &l )
: c_( l.hd_ ), l_( &l ) {}
virtual ~ListIter() {}
virtual void reset()
{ c_ = l_->hd_; }
virtual void next()
{ c_ = c_->n_; }
virtual bool done() const
{ return !c_; }
virtual T &get() const
{ return c_->e_; }
private:
ListNode<T> *c_;
const List<T> *l_;
};
template <typename T>
List<T>::~List() {
ListNode<T> *p = hd_, *q;
while( p ) {
q = p;
p = p->n_;
delete q;
}
}
template <typename T>
List<T> *List<T>::clone() const {
// not efficient, but interesting...
std::auto_ptr<List> n1( new List );
for( ListIter<T> i( *this ); !i.done(); i.next() )
n1->insert( i.get() );
List *n2( new List );
for( ListIter<T> i( *n1 ); !i.done(); i.next() )
n2->insert( i.get() );
return n2;
}
#endif
main.cpp
#include <memory>
#include <string>
#include <iostream>
#include "container.h"
#include "list.h"
template <typename T>
void print( Container<T> &c ) {
std::auto_ptr< Iter<T> > i( c.genIter() );
for( i->reset(); !i->done(); i->next() )
std::cout << i->get() << std::endl;
}
int main() {
std::auto_ptr< List<int> > c1( new List<int> );
for( int i = 0; i < 10; ++i )
c1->insert( i );
print( *c1 );
std::auto_ptr< Container<std::string> > c2( new List<std::string> );
c2->insert( "Andrei" );
c2->insert( "Dan" );
c2->insert( "Herb" );
c2->insert( "Scott" );
print( *c2 );
std::auto_ptr< Container<std::string> > c3( c2->clone() );
print( *c3 );
extern void victimize();
victimize();
getchar();
return 0;
}
输出
9
8
7
6
5
4
3
2
1
0
Scott
Herb
Dan
Andrei
Scott
Herb
Dan
Andrei
UnderpaidMinion::pay!