首页 > 代码库 > C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]

C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]

模板与泛型编程

--类模板成员[1]

二、非类型形参的模板实参

template <int hi,int wid>
class Screen
{
public:
    Screen():screen(hi * wid,‘#‘),
        cursor(hi * wid),height(hi),width(wid) {}

    //..

private:
    std::string screen;
    std::string::size_type cursor;
    std::string::size_type height,width;
};

这个模板有两个形参,均为非类型形参。当用户定义Screen对象时,必须为每个形参提供常量表达式以供使用。

    Screen<24,48> hp;	//hi的模板实参为24,wid=48

【注解】

非类型模板实参必须是编译时常量表达式。



三、类模板中的友元声明

在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体友元关系:

1)普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。

2)类模板或函数模板的友元声明,授予对友元所有实例的访问权。

3)只授予对类模板或函数模板特定实例的访问权的友元声明。



1、普通友元

template <typename Type> class Bar
{
    friend class FooBar;
    friend void fcn();
};

普通非模板类FooBar和函数fcn可以访问Bar类的任意private成员和protected成员。



2、一般模板友元关系

友元关系可以是类模板或函数模板:

template <typename Type> class Bar
{
    template <typename T> friend class Foo1;
    template <typename T> friend void temp_fcn1(const Type &);
};

这些友元声明使用与类本身不同的类型形参,该类型形参指的是Foo1temp1_fcn1的类型形参。在这两种情况下,都将没有数目限制的类和函数设为Bar的友元

这个友元声明在Bar与其友元Foo1temp_fcn1每个实例之间建立了一对多的映射。对Bar的每个实例而言,Foo1temp1_fcn1的所有实例都是友元。



3、特定的模板友元关系

只授予对特定实例的访问权:

template <typename Type> class Foo2;
template <typename Type>
void temp1_fcn2(const Type &);

template <typename Type> class Bar
{
    friend class Foo2<char *>;
    friend void temp1_fcn2<char *>(char * const &);
};

即使Foo2本身是类模板,友元关系也只扩展到Foo2的形参类型为char*实例

下面形式的友元更为常见:

template <typename Type> class Foo2;
template <typename Type>
void temp1_fcn2(const Type &);

template <typename T> class Bar
{
    friend class Foo3<T>;
    friend void temp1_fcn3<T>(const T &);
};

这些友元定义了Bar的特定实例与使用同一模板实参Foo3temp1_fcn3的实例之间的友元关系。每个Bar实例有一个相关的Foo3temp1_fcn3友元:

    Bar<int> bi;
    Bar<string> bs;

只有与给定 Bar实例有相同模板实参的那些Foo3temp1_fcn3版本是友元。因此,Foo3<int>可以访问Bar<int>的私有部分,但不能访问Bar<string>或者任意其他Bar实例的私有部分。



4、声明依赖性

当授予对给定模板的所有实例的访问权的时候,在作用域中不需要存在该类模板函数模板声明。实质上,编译器将友元声明也当做类或函数声明对待

想要限制对特定实例化友元关系时,必须在可以用于友元声明之前声明类或函数

template <class T> class A;
template <class T> class B
{
public:
    friend class A<T>;	//OK:类A已声明
    friend class C;		//OK:C为一个普通非模板类,相当于声明

    template <class S> friend class D;	//OK:D是一个泛化的模板类

    friend class E<T>;	//Error:E在此之前还没有声明或定义
    friend class F<int>;	//Error:同上
};

如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数



四、QueueQueueItem的友元声明

QueueItem不打算为一般程序所用它的所有成员都是私有的。为了让Queue类使用QueueItem类,QueueItem类必须将Queue声明为友元。



1、将类模板设为友元

对于实例化的Queue类的每种类型,我们想要Queue类和QueueItem类之间的一对一映射:

template <class Type> class Queue;
template <class Type> class QueueItem
{
    friend class Queue<Type>;
};


2Queue输出操作符

因为希望输出任意类型Queue的内容,所以需要将输出操作符也设为模板:

template <typename Type>
ostream &operator<<(ostream &os,const Queue<Type> &q)
{
    os << "< ";
    for (Queue<Type> *p = q.head; p ;
            p = p -> next)
    {
        os << p -> item << " ";
    }
    os << ">";

    return os;
}

如果Queue为空,则for循环不执行,结果输出一对尖括号。



3、将函数模板设为友元

输出操作符需要成为Queue类和QueueItem类的友元。因为它需要使用QueueheadQueueItemnextitem。我们的类将友元关系授予用同样类型实例化的输出操作符的特定实例:

template <typename Type> class Queue;
template <typename Type>
ostream &operator<<(ostream &,const Queue<Type> &); //首先声明

template <typename Type> class QueueItem
{
    friend class Queue<Type>;
    friend ostream &
    operator<< <Type>(ostream &,const Queue<Type> &);
    //AS Before
};

template <typename Type> class Queue
{
    friend ostream &
    operator<< <Type>(ostream &,const Queue<Type> &);
    //As Before
};

每个友元声明授予对对应operator<<实例的访问权。



4、类型依赖性与输出操作符

Queue的输出operator<<依赖于item对象的operator<<实际输出每个元素:

	os << p -> item << " "; 

当使用p-> item 作为<<操作符的操作数的时候,使用的是item所属的任意类型而定义的<<

因此,绑定到Queue且使用Queue输出操作符的每种类型本身必须有输出操作符。为没有定义输出操作符的类创建Queue对象是合法的,但输出保存这种类型的Queue对象会发生编译时(或链接时)错误

//P555 习题16.38/39/40
template <size_t hi,size_t wid>
class Screen;
template <size_t hi,size_t wid> ostream &
operator<<(ostream &,const Screen<hi,wid> &);
template <size_t hi,size_t wid> istream &
operator>>(istream &,Screen<hi,wid> &);

template <size_t hi,size_t wid>
class Screen
{
    friend ostream &
    operator<< <hi,wid>(ostream &,const Screen<hi,wid> &);

    friend istream &
    operator>> <hi,wid>(istream &,Screen<hi,wid> &);
public:
    typedef std::string::size_type index;

    Screen():
        screen(hi * wid,‘#‘),cursor(0),height(hi),width(wid) {}

private:
    string screen;
    index cursor;
    index height,width;
};

template <size_t hi,size_t wid>
ostream &operator<<(ostream &os,const Screen<hi,wid> &s)
{
    os << "screen: " << s.screen << "\t"
       << "height: " << s.height << "\t" << "width: " << s.width;
    return os;
}

template <size_t hi,size_t wid>
istream &operator>>(istream &in,Screen<hi,wid> &s)
{
    in >> s.screen >> s.height >> s.width;
    if (in)
    {
        s.cursor = s.height * s.width;
    }

    return in;
}

//习题16.42
template <typename Type>
istream &operator>>(istream &in,Queue<Type> &q)
{
    Type item;
    in >> item;
    q.push(item);

    return in;
}


五、成员模板

任意类都可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。

考虑Queue类的复制构造函数:它接受一个形参,Queue<Type>的引用。想要通过从vector对象中复制元素而创建Queue对象,是办不到的,因为没有从vectorQueue的转换。类似地,想要Queue<short>复制元素到Queue<int>,也办不到。同样的逻辑应用于赋值操作符,它也接受一个Queue<Type>&类型的形参。

我们希望定义一个构造函数和一个 assign成员,使容器类型元素类型都能变化

Queue例子中,我们将定义构造函数assign成员接受一对在其他序列指明范围的迭代器,这些函数将有一个表示迭代器类型模板类型形参

【注意】

标准queue类没有定义这些成员:不支持从其他容器建立queue对象或给 queue对象赋值。我们在这里定义这些成员只是为了举例说明



1、定义成员模板

模板成员声明看起来像任意模板的声明一样:

template <typename Type> class Queue
{
public:
    //函数形参是指明要复制元素范围的迭代器
    template <typename Iter>
    Queue(Iter beg,Iter end):head(0),tail(0)
    {
        copy_elems(beg,end);
    }

    template <typename Iter>
    void assign(Iter,Iter);
    //As Before

private:
	//As Before
    template <typename Iter>
    void copy_elems(Iter,Iter);
};

2、在类外部定义成员模板

当在类外定义成员模板的时候,必须包含两个模板形参表:

1assign成员:

//T:类模板形参;Iter:成员函数自己的模板形参
template <class T> template<class Iter>
void Queue<T>::assign(Iter beg,Iter end)
{
    destroy();
    copy_elems(beg,end);
}

2copy_elems成员:

template <typename Type> template<typename Iter>
void Queue<Type>::copy_elems(Iter beg,Iter end)
{
    while (beg != end)
    {
        push(*beg);
        ++ beg;
    }
}

【小心地雷】

因为assign函数删除现在容器中的成员,所以传给assign函数的迭代器有必要引用不同容器中的元素。标准容器的assign成员和迭代器构造函数有相同的限制。



3、成员模板遵循常规访问控制

成员模板遵循与任意其他类成员一样的访问规则。如果成员模板为私有的,则只有该类的成员函数和友元可以访问该成员模板。



4、成员模板和实例化

成员模板只有在程序中使用时实例化。只是类模板的成员模板实例化比类模板的普通成员函数的实例化要稍微复杂一些,成员模板有两种模板形参:由类定义的和由成员模板本身定义。其中:类模板形参由调用函数的对象的类型确定,成员定义的模板形参的行为与普通函数模板一样。这些形参都通过常规模板实参推断而确定。

理解下面一段程序:

    short a[4] = {0,3,6,9};
    Queue<int> qi(a,a + 4);
    vector<int> vi(a,a + 4);

    qi.assign(vi.begin(),vi.end());

  qi的定义将实例化:

void Queue<int>::Queue(short *, short *);

  qiassign成员的实例化:

void Queue<int>::assign(vector<int>::iterator,
                        vector<int>::iterator);