第15章 object-oriented programming, OOP
面向对象编程 :数据抽象,继承,动态绑定。
通过基类的引用或指针调用虚函数时,发生动态绑定。
除了构造函数之外,任意非 static 成员函数都可以是虚函数。保留字virtual只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。
protected不能被外部访问,但可被派生类访问。
private继承:【Derived对象】不能调用Base中的任何东西;所有继承方式【Derived类】都还能调用Base类中的public和protected的东西(成员函数)。Derived类的Derived则都无法调用。
-----------------------------------------------------------
private继承
基类成员 private成员 public成员 protected成员
内部访问 不可访问 可访问 可访问
对象访问 不可访问 不可访问 不可访问
------------------------------------------------------------
public继承
基类成员 private成员 public成员 protected成员
内部访问 不可访问 可访问 可访问
对象访问 不可访问 可访问 类定义中可访问,外部不可访问
------------------------------------------------------------
protected继承
基类成员 private成员 public成员 protected成员
内部访问 不可访问 可访问 可访问
对象访问 不可访问 不可访问 不可访问
------------------------------------------------------------
默认继承方式:
class Base { /* ... */ };
struct D1 : Base { /* ... */ }; // public inheritance by default
class D2 : Base { /* ... */ }; // private inheritance by default
派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
一旦函数在基类中声明为虚函数,它就一直为虚函数。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。
C++ 语言不要求编译器将对象的基类部分和派生部分和派生部分连续排列。
已定义的类才可以用作基类。如果已经声明了 Item_base 类,但没有定义它,则不能用 Item_base 作基类:
class Item_base; // declared but not defined
class Bulk_item : public Item_base { ... }; // error: Item_base must be defined
每个派生类包含并且可以访问其基类的成员,为了使用这些成员,派生类必须知道它们是什么。这一规则暗示着不可能从类自身派生出一个类。
如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。
通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。
友元关系不能继承。
//恢复基类成员的访问级别
class Base {
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base {
public:
//maintain access levels for members related to the size of the object
using Base::size;
protected:
using Base::n;
};
如果基类定义 static 成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例。
用派生类对象对基类对象进行初始化或赋值,非基类对象被切除。
如果是 public 继承,则用户代码和后代类都可以使用派生类到基类的转换。如果类是使用 private 或 protected 继承派生的,则用户代码不能将派生类型对象转换为基类对象。
如果是 private 继承,则从 private 继承类派生的类不能转换为基类。如果是 protected 继承,则后续派生类的成员可以转换为基类类型。
无论是什么派生访问标号,派生类本身都可以访问基类的 public 成员,因此,派生类本身的成员和友元总是可以访问派生类到基类的转换。
构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。
某些类需要只希望派生类使用的特殊构造函数,这样的构造函数应定义为protected。
如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。
// Base::operator=(const Base&) not invoked automatically
Derived &Derived::operator=(const Derived &rhs)
{
if (this != &rhs) { //防止自身赋值
Base::operator=(rhs); // assigns the base part
// do whatever needed to clean up the old value in the derived part
// assign the members from the derived
}
return *this;
}
析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。
编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。
对象的撤销顺序与构造顺序相反。
基类析构函数是三法则的一个重要例外。三法则指出,如果类需要析构函数,则类几乎也确实需要其他复制控制成员。基类几乎总是需要构造函数,从而可以将析构函数设为虚函数。如果基类为了将析构函数设为虚函数则具有空析构函数,那么,类具有析构函数并不表示也需要赋值操作符或复制构造函数。
构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。
将类的赋值操作符设为虚函数很可能会令人混淆,而且不会有什么用处。因为虚函数必须在基类和派生类中具有同样的形参。
在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。构造派生类对象时首先运行基类构造函数初始化对象的基类部分。撤销派生类对象时,首先撤销它的派生类部分。
如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。
继承情况下,派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。
struct Base {
int memfcn();
};
struct Derived : Base {
int memfcn(int); // hides memfcn in the base
};
d.memfcn(); // error: memfcn with no arguments is hidden 找名字 memfcn,并在 Derived 类中找到。一旦找到了名字,编译器就不再继续查找了
d.Base::memfcn(); // ok: calls Base::memfcn
局部作用域中声明的函数不会重载全局作用域中定义的函数,同样,派生类中定义的函数也不重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数。
如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。
如果派生类想通过自身类型使用重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义。也可为重载成员提供 using 声明。
class Base {
public:
virtual int fcn();
};
class D1 : public Base {
public:
// hides fcn in the base; this fcn is not virtual
int fcn(int); // parameter list differs from fcn in Base
// D1 inherits definition of Base::fcn()
};
class D2 : public D1 {
public:
int fcn(int); // nonvirtual function hides D1::fcn(int)
int fcn(); // redefines virtual fcn from Base
};
从 Base 继承的虚函数不能通过 D1 对象(或 D1 的引用或指针)调用,因为该函数被 fcn(int) 的定义屏蔽了。
通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:
Base bobj; D1 d1obj; D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn(); // ok: virtual call, will call Base::fcnat run time
bp2->fcn(); // ok: virtual call, will call Base::fcnat run time
bp3->fcn(); // ok: virtual call, will call D2::fcnat run time
//句柄类存储和管理基类指针,使用计数来管理
// use counted handle class for the Item_base hierarchy
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; // 指向使用计数
// called by both destructor and assignment operator to free pointers
void decr_use()
{ if (--*use == 0) { delete p; delete use; } }
};
Sales_item::Sales_item(const Item_base &item):
p(item.clone()), use(new std::size_t(1)) { }
// use-counted assignment operator; use is a pointer to a shared use count
Sales_item&
Sales_item::operator=(const Sales_item &rhs)
{
++*rhs.use;
decr_use();
p = rhs.p;
use = rhs.use;
return *this;
}
class Item_base {
public:
virtual Item_base* clone() const
{ return new Item_base(*this); }
std:string book() const { return isbn;}
virtual double net_price(std:size_t n) const {}
private:
std:string isbn;
};
class Bulk_item : public Item_base {
//若虚函数的基类实例返回类类型的引用或指针,则该派生类可返回派生类的指针或引用
public:
Bulk_item* clone() const
{ return new Bulk_item(*this); }
double net_price(std:size_t n) const {}
};
// compare defines item ordering for the multiset in Basket
inline bool
compare(const Sales_item &lhs, const Sales_item &rhs)
{
return lhs->book() < rhs->book(); //
}
// type of the comparison function used to order the multiset
typedef bool (*Comp)(const Sales_item&, const Sales_item&);
std::multiset<Sales_item, Comp> items(compare);
/*items 是一个 multiset,它保存 Sales_item 对象并使用 Comp 类型的对象比较它们。multiset 是空的,但我们的确提供了一个比较函数 compare。当在 items 中增加或查找元素时,将用 compare 函数对 multiset 进行排序。*/
class Basket {
// type of the comparison function used to order the multiset
typedef bool (*Comp)(const Sales_item&, const Sales_item&);
public:
// make it easier to type the type of our set
typedef std::multiset<Sales_item, Comp> set_type;
// typedefs modeled after corresponding container types
typedef set_type::size_type size_type;
typedef set_type::const_iterator const_iter;
Basket(): items(compare) { } // initialze the comparator
void add_item(const Sales_item &item)
{ items.insert(item); }
size_type size(const Sales_item &i) const
{ return items.count(i); }
double total() const; // sum of net prices for all items in the basket
private:
std::multiset<Sales_item, Comp> items;
};
double Basket::total() const {
double sum = 0.0; // holds the running total
for (const_iter iter = items.begin();
iter != items.end();
iter = items.upper_bound(*iter))
//返回指向与该键相同的最后一个元素的下一元素
{
sum += (*iter)->net_price(items.count(*iter));
}
return sum;
}
15.9程序在本文最后
函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前。
template <typename T> inline T min(const T&, const T&);
用作模板形参的名字不能在模板内部重用。
template <class T> T calc(const T &a, const T &b) {
typedef double T; // error: redeclares template parameter T
// ... }
// error: illegal reuse of template parameter name V
template <class V, class V> V calc(const V&, const V&) ;
正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:
// ok: reuses parameter type name across different templates
template <class T> T calc (const T&, const T&) ;
template <class T> int compare(const T&, const T&) ;
template <class Parm, class U>
Parm fcn(Parm* array, U value) {
Parm::size_type * p; // If Parm::size_type is a type, then a declaration
// If Parm::size_type is an object, then multiplication
}
//我们不知道 size_type 是一个类型成员的名字还是一个数据成员的名字,
//默认情况下,编译器假定这样的名字指定数据成员,而不是类型
//如果希望编译器将 size_type 当作类型,则必须显式告诉编译器这样做:
template <class Parm, class U>
Parm fcn(Parm* array, U value) {
typename Parm::size_type * p; // ok: declares p to be a pointer
}
template <class T, size_t N> void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参
int x[42];
array_init(x); // instantiates array_init(int(&)[42]
编译器只会执行两种转换:
1.const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。
2.数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
template <typename T> T fobj(T, T); // arguments are copied
template <typename T>
T fref(const T&, const T&); // reference arguments
string s1("a value");
const string s2("another value");
fobj(s1, s2); // ok: calls f(string, string), const is ignored
fref(s1, s2); // ok: non const object s1 converted to const reference
int a[10], b[42];
fobj(a, b); // ok: calls f(int*, int*)
fref(a, b); // error: array types don't match; arguments aren't converted to pointers 当形参为引用时,数组不能转换为指针
可以使用函数模板对函数指针进行初始化或赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。
template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare (const int&, const int&)
int (*pf1) (const int&, const int&) = compare;
在返回类型中引入第三个模板形参,它必须由调用者显式指定。
template <class T1, class T2, class T3>
T1 sum(T2, T3);
long val3 = sum<long>(i, lng); // ok: calls long sum(int, long)
显式模板实参从左至右对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推。
template <class T1, class T2, class T3>
T3 alternative_sum(T2, T1);
// error: can't infer initial template parameters
long val3 = alternative_sum<long>(i, lng);
// ok: All three parameters explicitly specified
long val2 = alternative_sum<long, int, long>(i, lng);
模板编译模型:当编译器看到模板定义的时候,它不立即产生代码。只有在看到用到模板时,如调用了函数模板或调用了类模板的对象的时候,编译器才产生特定类型的模板实例。
一般而言,当调用函数的时候,编译器只需要看到函数的声明。类似地,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。
1.包含编译模型:这一策略使我们能够保持头文件和实现文件的分享,但是需要保证编译器在编译使用模板的代码时能看到两种文件。
//sum.h
#ifndef SUM
#define SUM
template<typename T1>
T1 sum(T1 a,T1 b);
#include "sum.cpp"
#endif
//sum.cpp
template<typename T1>
T1 sum(T1 a,T1 b) {
return a+b;
}
//main.cpp
#include "sum.h"
#include <iostream>
int main() {
std::cout << sum(1, 2) << std::endl;
return 0;
}
2.分别编译模型: 使用export关键字,必须写在template前面,和inline不能同时使用
VS2010都还不支持。
在类外定义模板类的成员函数
template <class T> ret-type Queue<T>::member-name //类名必须包含其模板形参。
何时实例化类和成员:
类模板的成员函数只有为程序所用才进行实例化,类模板的指针定义不会对类进行实例化,只有用到这样的指针时才会对类进行实例化。
template <int hi, int wid> //非类型模板实参必须是编译时常量表达式。
class Screen {};
友元声明依赖性:
当授予对给定模板的实例的访问权时候,在作用域中不需要存在该类模板或函数模板的声明。
想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数。如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。
template <class T> class A;
template <class T> class B {
public:
friend class A<T>; // ok: A is known to be a template
friend class C; // ok: C must be an ordinary, nontemplate class
template <class S> friend class D; // ok: D is a template
friend class E<T>; // error: E wasn't declared as a template
friend class F<int>; // error: F wasn't declared as a template
};
// declaration that Queue is a template needed for friend declaration in QueueItem
template <class Type> class Queue; //必须声明
template <class Type> class QueueItem {
friend class Queue<Type>; //一对一映射
// ...
};
成员模板不能为虚.
类模板的 static 成员:一样只在使用时才进行初始化,必须在类外定义。
template <class T> size_t Foo<T>::ctr = 0; // define and initialize ctr
Queue模板:
//Queue.h
#ifndef queue_h
#define queue_h
// declaration that Queue is a template needed for friend declaration in QueueItem
template <class Type> class Queue;
// function template declaration must precede friend declaration in QueueItem
template <class T>
std::ostream& operator<<(std::ostream&, const Queue<T>&);
template <class Type> class QueueItem {
friend class Queue<Type>;
// needs access to item and next
friend std::ostream& operator<< <Type> (std::ostream&, const Queue<Type>&);
// private class: no public section
QueueItem(const Type &t): item(t), next(0) {}
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};
template <class Type> class Queue {
// needs access to head
friend std::ostream& operator<< <Type> (std::ostream&, const Queue<Type>&);
public:
// empty Queue
Queue(): head(0), tail(0) { }
// construct a Queue from a pair of iterators on some sequence
template <class It>
Queue(It beg, It end):
head(0), tail(0) { copy_elems(beg, end); }
// copy control to manage pointers to QueueItems in the Queue
Queue(const Queue &Q): head(0), tail(0)
{ copy_elems(Q); }
Queue& operator=(const Queue&); // left as exercise for the reader
~Queue() { destroy(); }
// replace current Queue by contents delimited by a pair of iterators
template <class Iter> void assign(Iter, Iter);
// return element from head of Queue
// unchecked operation: front on an empty Queue is undefined
Type& front() { return head->item; }
const Type &front() const { return head->item; }
void push(const Type &);
void pop();
bool empty() const { // true if no elements in the Queue
return head == 0;
}
private:
QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue
// utility functions used by copy constructor, assignment, and destructor
void destroy();
void copy_elems(const Queue&);
// version of copy to be used by assign to copy elements from iterator range
template <class Iter> void copy_elems(Iter, Iter);
};
// Inclusion Compilation Model: include member function definitions as well
template <class Type>
std::ostream& operator<< (std::ostream &os, const Queue<Type> &q) {
os << "< ";
QueueItem<Type> *p;
for (p = q.head; p; p = p->next)
os << p->item << " ";
os <<">";
return os;
}
template <class Type> void Queue<Type>::destroy() {
while (!empty())
pop();
}
template <class Type> void Queue<Type>::pop() {
// pop is unchecked: Popping off an empty Queue is undefined
QueueItem<Type> *p = head; // keep pointer to head so we can delete it
head = head->next; // head now points to next element
delete p; // delete old head element
}
template <class Type> void Queue<Type>::push(const Type &val) {
// allocate a new QueueItem object
QueueItem<Type> *pt = new QueueItem<Type>(val);
// put item onto existing queue
if (empty())
head = tail = pt; // the queue now has only one element
else {
tail->next = pt; // add new element to end of the queue
tail = pt;
}
}
template <class Type> template <class It> //定义必须包含类模板形参以及自己的模板形参
void Queue<Type>::copy_elems(It beg, It end) {
while (beg != end) {
push(*beg);
++beg;
}
}
template <class T> template <class Iter>
void Queue<T>::assign(Iter beg, Iter end) {
destroy(); // remove existing elements in this Queue
copy_elems(beg, end); // copy elements from the input range
}
template <class Type>
void Queue<Type>::copy_elems(const Queue &orig) {
// copy elements from orig into this Queue
// loop stops when pt == 0, which happens when we reach orig.tail
for (QueueItem<Type> *pt = orig.head; pt; pt = pt->next)
push(pt->item); // copy the element
}
template <class Type>
Queue<Type>& Queue<Type>::operator=(const Queue &rhs) {
destroy();
head=rhs.head;
tail=rhs.tail;
return *this;
}
#endif
//main.cpp
#include <iostream>
#include "Queue.h"
int main() {
short a[4] = { 0, 3, 6, 9 };
Queue<int> qi(a, a + 4); // copies elements from a into qi
std::vector<int> vi(a, a + 4);
qi.assign(vi.begin(), vi.end());
std::cout<<qi;
return 0;
}
句柄类模板
template <class T> class Handle {
public:
// unbound handle
Handle(T *p = 0): ptr(p), use(new size_t(1)) { }
// overloaded operators to support pointer behavior
T& operator*();
T* operator->();
const T& operator*() const;
const T* operator->() const;
// copy control: normal pointer behavior, but last Handle deletes the object
Handle(const Handle& h): ptr(h.ptr), use(h.use)
{ ++*use; }
Handle& operator=(const Handle&);
~Handle() { rem_ref(); }
private:
T* ptr; // shared object
size_t *use; // count of how many Handle spointto *ptr
void rem_ref()
{ if (--*use == 0) { delete ptr; delete use; } }
};
template <class T>
inline Handle<T>& Handle<T>::operator=(const Handle &rhs) {
++*rhs.use; // protect against self-assignment
rem_ref(); // decrement use count and delete pointers if needed
ptr = rhs.ptr;
use = rhs.use;
return *this;
}
template <class T> inline T& Handle<T>::operator*() {
if (ptr) return *ptr;
throw std::runtime_error
("dereference of unbound Handle");
}
template <class T> inline T* Handle<T>::operator->() {
if (ptr) return ptr;
throw std::runtime_error
("access through unbound Handle");
}
1.函数模板的特化template <> 返回类型 模板名<特化定义的模板形参>(函数形参表){}
函数重载与模板特化:在特化中省略 template<> ,则会声明重载版本。
当特化模板的时候,对实参类型不应用转换。
在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不完全匹配,编译器将为实参从模板定义实例化一个实例。
2.类模板的特化
3.特化成员而不特化类
4.类模板的部分特化
template <class T1, class T2>
class some_template {
// ...
};
// partial specialization: fixes T2 as int and allows T1 to vary
template <class T1>
class some_template<T1, int> {
// ...
};
函数模板可以重载:可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。
//15.9文本查询
class TextQuery {
public:
typedef string::size_type str_size;
typedef vector<string>::size_type line_no;
void read_file(ifstream &is) {store_file(is); build_map();}
set<line_no> run_query(const string&) const;
string text_line(line_no) const;
line_no size() const;
private:
void store_file(ifstream&);
void build_map();
vector<string> lines_of_text;
map< string, set<line_no> > word_map;
static string cleanup_str(const string&);
};
void TextQuery::store_file(ifstream &is)
{
string textline;
while (getline(is, textline))
lines_of_text.push_back(textline);
}
void TextQuery::build_map()
{
for (line_no line_num = 0;
line_num != lines_of_text.size();
++line_num)
{
istringstream line(lines_of_text[line_num]);
string word;
while (line >> word)
word_map[cleanup_str(word)].insert(line_num);
}
}
string TextQuery::text_line(line_no line) const
{
if(line < lines_of_text.size())
return lines_of_text[line];
throw out_of_range("line number out of range");
}
string TextQuery::cleanup_str(const string &word)
{
string ret;
for (string::const_iterator it = word.begin();
it != word.end(); ++it)
{
if (!ispunct(*it))
ret += tolower(*it);
}
return ret;
}
TextQuery::line_no TextQuery::size() const
{
return lines_of_text.size();
}
set<TextQuery::line_no> TextQuery::run_query(const string &query_word) const
{
map<string, set<line_no> >::const_iterator
loc=word_map.find(query_word);
if (loc == word_map.end())
return set<line_no>();
else
return loc->second;
}
class Query_base {
friend class Query;
protected:
typedef TextQuery::line_no line_no;
virtual ~Query_base() { }
private:
virtual set<line_no> eval(const TextQuery&) const = 0;
virtual ostream& display(ostream& = cout) const = 0;
};
class WordQuery: public Query_base {
friend class Query; // Query uses the WordQuery constructor
WordQuery(const std::string &s): query_word(s) { }
// concrete class: WordQuery defines all inherited pure virtual functions
set<line_no> eval(const TextQuery &t) const
{ return t.run_query(query_word); }
ostream& display (std::ostream &os) const
{ return os << query_word; }
string query_word; // word for which to search
};
inline ostream& operator<<(std::ostream &os, const Query &q)
{
return q.display(os);
}
// handle class to manage the Query_base inheritance hierarchy
class Query {
// these operators need access to the Query_base* constructor
friend Query operator~(const Query &);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
Query(const string&); // builds a new WordQuery
// copy control to manage pointers and use counting
Query(const Query &c): q(c.q), use(c.use) { ++*use; }
~Query() { decr_use(); }
Query& operator=(const Query&);
// interface functions: will call corresponding Query_base operations
std::set<TextQuery::line_no>
eval(const TextQuery &t) const { return q->eval(t); }
std::ostream &display(ostream &os) const
{ return q->display(os); }
private:
Query(Query_base *query): q(query),
use(new size_t(1)) { }
Query_base *q;
size_t *use;
void decr_use()
{
if (--*use == 0) { delete q; delete use; }
}
};
Query::Query(const string &s):q(new WordQuery(s)),
use(new size_t(1)){}
Query& Query::operator=(const Query &rhs)
{
++*rhs.use;
decr_use();
q=rhs.q;
use=rhs.use;
return *this;
}
class BinaryQuery: public Query_base {
protected:
BinaryQuery(Query left, Query right, std::string op):
lhs(left), rhs(right), oper(op) { }
// abstract class: BinaryQuery doesn't define eval
ostream& display(ostream &os) const
{ return os << "(" << lhs << " " << oper << " "<< rhs << ")"; }
const Query lhs, rhs; // right- and left-hand operands
const std::string oper; // name of the operator
};
class AndQuery: public BinaryQuery {
friend Query operator&(const Query&, const Query&);
AndQuery (Query left, Query right):
BinaryQuery(left, right, "&") { }
// concrete class: And Query inherits display and defines remaining pure virtual
std::set<line_no> eval(const TextQuery&) const;
};
class NotQuery: public Query_base {
friend Query operator~(const Query &);
NotQuery(Query q): query(q) { }
// concrete class: NotQuery defines all inherited pure virtual functions
std::set<line_no> eval(const TextQuery&) const;
std::ostream& display(std::ostream &os) const
{ return os << "~(" << query << ")"; }
const Query query;
};
class OrQuery: public BinaryQuery {
friend Query operator|(const Query&, const Query&);
OrQuery(Query left, Query right):
BinaryQuery(left, right, "|") { }
// concrete class: OrQuery inherits display and defines remaining pure virtual
set<line_no> eval(const TextQuery&) const;
};
inline Query operator&(const Query &lhs, const Query &rhs)
{
return new AndQuery(lhs, rhs);
}
inline Query operator|(const Query &lhs, const Query &rhs)
{
return new OrQuery(lhs, rhs);
}
inline Query operator~(const Query &oper)
{
return new NotQuery(oper);
}
// returns union of its operands' result sets
set<TextQuery::line_no> OrQuery::eval(const TextQuery& file) const
{
// virtual calls through the Query handle to get result sets for the operands
set<line_no> right = rhs.eval(file),
ret_lines = lhs.eval(file); // destination to hold results
// inserts the lines from right that aren't already in ret_lines
ret_lines.insert(right.begin(), right.end());
return ret_lines;
}
// returns intersection of its operands' result sets
set<TextQuery::line_no> AndQuery::eval(const TextQuery& file) const
{
// virtual calls through the Query handle to get result sets for the operands
set<line_no> left = lhs.eval(file),
right = rhs.eval(file);
set<line_no> ret_lines; // destination to hold results
// writes intersection of two ranges to a destination iterator
// destination iterator in this call adds elements to ret
set_intersection(left.begin(), left.end(),
right.begin(), right.end(),
inserter(ret_lines, ret_lines.begin()));
return ret_lines;
}
// returns lines not in its operand's result set
set<TextQuery::line_no> NotQuery::eval(const TextQuery& file) const
{
// virtual call through the Query handle to eval
set<TextQuery::line_no> has_val = query.eval(file);
set<line_no> ret_lines;
// for each line in the input file, check whether that line is in has_val
// if not, add that line number to ret_lines
for (TextQuery::line_no n = 0; n != file.size(); ++n)
if (has_val.find(n) == has_val.end())
ret_lines.insert(n);
return ret_lines;
}
string make_plural(size_t ctr,const string &word, const string &ending)
{
return (ctr==1) ? word : word+ending;
}
ifstream& open_file(ifstream &in, const string &file)
{
in.close(); // close in case it was already open
in.clear(); // clear any existing errors
// if the open fails, the stream will be in an invalid state
in.open(file.c_str()); // open the file we were given
return in; // condition state is good if open succeeded
}
void printf_results(const set<TextQuery::line_no> &locs, const TextQuery &file)
{
typedef set<TextQuery::line_no> line_nums;
line_nums::size_type size = locs.size();
cout << "match occurs "
<< size << ""
<<make_plural(size, "time","s") << endl;
line_nums::const_iterator it = locs.begin();
for(; it != locs.end(); ++it)
{
cout << "\t(line "
<< (*it) + 1 << ") "
<< file.text_line(*it) <<endl;
}
}
int main(int argc, char **argv)
{
ifstream infile;
if (argc < 2 || !open_file(infile, argv[1]))
{
cerr << "No input file" <<endl;
return EXIT_FAILURE;
}
TextQuery file;
file.read_file(infile);
typedef set<TextQuery::line_no> line_nums;
Query q = Query("fiery") & Query("bird") | Query("wind");
const line_nums &locs = q.eval(file);
cout << "\nExecuted Query for: "<< q << endl;
printf_results(locs,file);
return 0;
}
generic handle class 泛型句柄类
inclusion compilation model 包含编译模型
instantiation 实例化
partial specialization 部分特化