Test Header HTML Code

关于C++模板的几点小结

我一直认为模板是C++语言的精髓,为我们提供了无比强大的泛型编程功能。

  • 数据类型级别的代码复用,使用模板为不同的数据类型提供相关类和函数实现。这种复用是在编译阶段的复用,编译器对模板进行实例化,自动生成若干相应的类和函数,避免重复编码。
  • Java和C#中的对象基本上都是引用类型(引用其实相当于一种特殊的指针,其内存大小等特征是一定的),并且都继承一个object基类,设计的类和算法很容易操纵所有的数据类型。而C++中基本都是值类型,其内存大小等特征不一样,因此需要进行严格类型检验,所以需要模板机制来为不同的数据类型提供相同的接口和实现,模板机制可以看做是一种编译阶段的多态特征,会为每一个数据类型参数生成一个模板类或函数。
  • 模板可以看作成一种元数据类型,提供了一种根据参数生成类和函数的模板。

一、模板定义(Template Definition)

  1. 类模板
    //declaration
    template<class C> class String /*<class C>也可以写成<typename C>*/
    {
    public:
        String(const C*);
        C read(int i) const;
    private:
        C *base;
    };

    //implementation
    template<class C> String::String(const C*)
    {
        …
    }

    template<class C> C String::read(int i) const
    {
        …
    }
  2. 函数模板
    template<class T > copy(const std::vector<T> &, std::vector<T> &)
    {
        …
    }
    复杂些的定义:
    template<class T, class C, C i> …
    template<class T, int size> …

    根据《The C++ Programming Language》,模板的声明和定义可以分别放在.h和.cpp文件中,但是我发现目前GCC和VC都不支持这种代码安排方式,因此一般采用将其声明和实现同时放在一个hpp或者cpp文件中。

二、 模板的使用和实例化(Template Instantiation)

    模板形参和实参(Template Parameter and Argument):
        template<class T> class String;
       String<char> str;
       String<wchar_t> wstr;

    其中T为形参,char和wchar_t为实参。

    模板实例化就是根据实参,自动生成模板实例的过程。以上例子就会生成两个模板类String<char>, String<wchar_t>, 相当于根据char和wchar_t将String<T>的代码生成了两份。

    实例化的一个重要原则是:未被使用的成员函数将不会被实例化,即最后生成的文件中不包含该模板成员函数。这同时也引出了另外一个问题,编译器在对模板进行语义检查的时候,也不会检查该未使用的成员函数,因此只有其被使用的时候才会检查其错误。

    模板的实参可以是:常量表达式, 具有外部链接的对象或函数地址, 非重载的函数指针, 模板。

    通过typedef以及常量表达式计算结果相同的参数对应的模板实例相同。比如:

    template<class T, int i> class Buffer;
    Buffer<char, 10> cbuffer; //具有10个字符的缓冲区,将缓冲区大小作为参数,可以在编译时候确定缓冲区大小,而不需要运行时进行new和delete操作。
    typedef unsigned char uchar;
    Buffer<unsigned char, 20> 与 Buffer<uchar, 20>等效,是同一个类。
    Buffer<char, 20-10> 与 Buffer<char, 10>等效。

三、 函数模板的参数推导(deduction)

    使用类模板的时候,都需要显示制定模板实参,但是在使用函数模板的时候,不需要显示指定,编译器会根据传入函数的实参数据类型推导出函数模板的实参(Function arguments->Template arguments),从而完成函数的实例化。

    当然使用函数模板也可以显示指定模板实参,全部或者一部分,如果函数模板的部分实参可以根据函数调用推导出来,那么只需要指定剩下不能推导出来的模板实参。这里也有一个跟函数缺省参数类似的原则,就是显示指定的实参只能是位于左边,右边未指定的需要推导。如:

template<class T, class C> T implicit_cast(C c) { return c; }

int i = implicit_cast<int>(2.0); //其实就是调用implicit<int, double>(2.0);

template<class T> T* create() { return new T(); }

int k = create<int>(); //参数是返回值,需要显示指定

四、函数模板可以重载

    与类和函数的规则相似,类模板不允许重载,即一个类模板的名字不允许出现多次(不过模板特殊化的时候例外Specialization),但是函数模板可以重载,通过给定不同的模板形参组合定义多个函数模板。因此在函数模板进行实例化的时候,就有一个Overload Resolution的过程,即如何根据函数实参推导出最合适的函数模板。

template<class T> T sqrt(T);

template<class T> complex<T> sqrt(complex<T>);

double sqrt(double);

complex<double> z(1.0, 2.0);

sqrt(2); //sqrt<int>(int)

sqrt(2.0);//sqrt(double);

sqrt(z); //sqrt<double>(complex<double>);

    首先推导出每一个可能的候选模板函数:

如sqrt(z) 得到两个候选:sqrt<double>(complex<double>)和sqrt<complex<double>>(complex<double>)

优先顺序:

1. 普通函数。如:sqrt(double)优于sqrt<double>(double);

2. 最特殊化的,如:template<class T> complex<T> sqrt(complex<T>);比template<class T> T sqrt(T);更加特殊化,所以前者优先。

解决歧义的方法:

1. 显示指定模板参数

2. 进一步重载,不过以内联函数的方式,避免多级别的函数调用带来的开销。如:

template<class T> T max(T, T);

max(1, 2.0); //产生歧义,max<int>(int, int) or max<double>(double, double)?

解决方法:

1. max<double>(1, 2.0)

2. inline double max(int, double) { return max<double>(1, 2.0); }

posted on 2010-03-28 15:04  宁静的水泡  阅读(1414)  评论(0编辑  收藏  举报

导航

Test Rooter HTML Code