天天看點

深入了解模闆

深入模闆

一、相關日志

1、模闆介紹(Introduction to Templates)

http://blog.163.com/zhoumhan_0351/blog/static/399542272010321103820560

2、C++基本概念(内聯,模闆,函數)

http://blog.163.com/zhoumhan_0351/blog/static/39954227201002202430247

二、深入模闆

    一個無類型模闆參數必須是一個編譯時所知的整數。

template<class T, size_t N> class Stack {

  T data[N];  // Fixed capacity is N

  size_t count;

public:

  void push(const T& t);

  // Etc.

};

Stack<int, 100> myFixedStack;

由于N的值在編譯時是已知的,内含的資料可以被置于進行時堆棧而不是動态存儲空間。

在類模闆中,可以為模闆參數提供預設參數,但是在函數模闆中不行。如

template<class T, class Allocator = allocator<T> >

class vector;

任何時候預設了第二個參數,則用預設的标準allocator模闆進行配置設定。

盡管不能在函數模闆中使用預設的模闆參數,卻能夠用模闆參數作為普通函數的預設參數。如

#include <iostream>

using namespace std;

template<class T> 

T sum(T* b, T* e, T init = T()) {

  while(b != e)

    init += *b++;

  return init;

}

int main() {

  int a[] = { 1, 2, 3 };

  cout << sum(a, a + sizeof a / sizeof a[0]) << endl; // 6

} ///:~

1、模闆類型的模闆參數

template<class T, template<class U> class Seq>

其中的U可以省略。

2、When the compiler looks at the inner parameters of a template template 

parameter, default arguments are not considered, so you have to repeat 

the defaults in order to get an exact match.This is the only exception to 

the rule stated earlier that default arguments should appear only once in a 

compilation unit. 

#include <cstddef>

#include <iostream>

using namespace std;

template<class T, size_t N = 10>  // A default argument

class Array {

  T data[N];

  size_t count;

public:

  Array() { count = 0; }

  void push_back(const T& t) {

    if(count < N)

      data[count++] = t;

  }

  void pop_back() {

    if(count > 0)

      --count;

  }

  T* begin() { return data; }

  T* end() { return data + count; }

};

template<class T, template<class, size_t = 10> class Seq>

class Container {

  Seq<T> seq;  // Default used

public:

  void append(const T& t) { seq.push_back(t); }

  T* begin() { return seq.begin(); }

  T* end() { return seq.end(); }

};

int main() {

  Container<int, Array> container;

  container.append(1);

  container.append(2);

  int* p = container.begin();

  while(p != container.end())

    cout << *p++ << endl;

} ///:~

3、The default behavior of the compiler is to assume that a name is not a 

type, you must use typename for nested names (except in constructor 

initializer lists, where it is neither needed nor allowed).

    if a type referred to inside template code is qualified by a template type parameter, you must use the typename keyword as a prefix, unless it appears in a base class specification or initializer list in the same scope (in which case you must not).

for(typename Seq<T>::iterator b = seq.begin();//說明iterator是一個類型,不然編譯器會把它作為一個靜态資料成員

       b != seq.end();)

typedef的三種用法

(1)Typedefing a typename//說明下面定義的是一個類型,不是建立了。

typename Seq<T>::iterator It;

(2)可以用來代替class,使模闆定義中的參數清單意義更明确

template<typename T> class X {};

4、'<' '>'可以用為小于,大于号,還可以做執行個體化時的辨別,及模闆的界定符,是以在某些情況下,我們也需要明确指出它是什麼。

template<class charT, size_t N>

basic_string<charT> bitsetToString(const bitset<N>& bs) {

  return bs. template to_string<charT, char_traits<charT>,

                                allocator<charT> >();

}

如果有明确用template指出,則會把<作為小于号。The template keyword 

used in this context tells the compiler that what follows is the name of a 

template, causing the < character to be interpreted correctly. 

5、成員模闆

template<typename T> class complex {

public:

  template<class X> complex(const complex<X>&);

  complex<float> z;

  complex<double> w(z);//T為double,X為float

模闆嵌套

template<typename T>

template<typename X>

complex<T>::complex(const complex<X>& c) {}

成員模闆可以是類,不一定是函數

template<class T> class Outer {

public:

  template<class R> class Inner {

  public:

    void f();

  };

};

注意:Member template functions cannot be declared virtual. Current compiler technology expects to be able to determine the size of a class’s virtual function table when the class is parsed. Allowing virtual member template functions would require knowing all calls to such member functions everywhere in the program ahead of time. This is not feasible, especially for multi-file projects.

6、幾個問題

    You must always use angle brackets when instantiating class templates and you must supply all non-default template arguments. However, with function templates you can often omit the template arguments, and default template arguments are not even allowed. 

    對于一個由模闆參數來限定類型的函數模闆,C++系統不能提供标準轉換。No standard conversions are applied for function arguments whose type is specified by a template parameter.

   數組維數沒有被作為函數參數類型的一部分進行傳遞,除非這個參數是指針或引用。

void init2(T (&a)[R][C]) {}

7、函數模闆重載

可以用相同的函數名重載函數模闆。

template<typename T> const T& min(const T& a, const T& b) {

  return (a < b) ? a : b;

}

const char* min(const char* a, const char* b) {

  return (strcmp(a, b) < 0) ? a : b;

}

cout << min<>(s1, s2) << endl;  //<>來用強制使用模闆

cout << min(s1, s2) << endl; //使用重載的那個(第二個),而不是模闆

    在使用過和中,尋找最佳比對,即如果重載了函數模闆,且類型比對,則用重載的函數模闆,而不會再去執行個體化模闆。

(1)以一個已知的函數模闆位址作為參數

  transform(s.begin(), s.end(), s.begin(), tolower);

但由于tolower有兩個版本,可能會問題。解決方法:

法1:

transform(s.begin(),s.end(),s.begin(),

            static_cast<int (*)(int)>(tolower));

利用強制類型轉換,表達一個參數的願望

法2:

明确的語境中使用

法3:

寫一個封裝的函數模闆

transform(s.begin(),s.end(),s.begin(),&strTolower<char>);

(2)函數模闆的半有序

An ordering is defined for function templates, which chooses the most specialized template, if such exists. A function template is considered more specialized than another if every possible list of arguments that matches it also matches the other, but not the other way around. 

template<class T> void f(T);

template<class T> void f(T*);

template<class T> void f(const T*);

    如果有一組模闆中沒有特化程式最高的模闆,則會出現二義性。

8、模闆特化

(1)顯式特化

#include <cstring>

#include <iostream>

using std::strcmp;

using std::cout;

using std::endl;

template<class T> const T& min(const T& a, const T& b) {

  return (a < b) ? a : b;

}

// An explicit specialization of the min template

template<>

const char* const& min<const char*>(const char* const& a,const char* const& b) {

  return (strcmp(a, b) < 0) ? a : b;

}

int main() {

  const char *s2 = "say \"Ni-!\"", *s1 = "knights who";

  cout << min(s1, s2) << endl;//都是調用第二個模闆

  cout << min<>(s1, s2) << endl;

} ///:~

template<>說明是一個顯式特化。

(2)半特化

隻特化其中的一部分。

template<class T, class U> class C {

public:

  void f() { cout << "Primary Template\n"; }

};

template<class U> class C<int, U> {

public:

  void f() { cout << "T == int\n"; }

};

In other words, specialization, and partial specialization in particular, constitute a sort of “overloading” for class templates.

就當選擇特化程式最高的模闆。

(3)從一個模闆派生另外一個模闆

template<class T>

class Sortable : public std::vector<T> {

public:

  void sort();

};

You can factor the bulk of the implementation for storing pointer types into a single class by using a combination of full and partial specialization. The key is to fully specialize for void* and then derive all other pointer types from the void* implementation so the common code can be shared.

void* top() const {

    assert(count > 0);

    return data[count-1];

  }

T* top() const { return static_cast<T*>(Base::top()); }

9、名字查找

模闆編譯分兩個階段進行。

In the first phase, the compiler parses the template definition looking for obvious syntax errors and resolving all the names it can.

    So instantiation is the second phase of template compilation. Here, the compiler determines whether to use an explicit specialization of the template instead of the primary template.

    限定名,是指具有類名前辍,或者是被一個對象名加上點運算符(or ->)修飾。

MyClass::f();

x.f();

p->f();

ADL specifies that when an unqualified function call appears and its declaration is not in (normal) scope, the namespaces of each of its arguments are searched for a matching function declaration.

std::cout << s;

operator<<(std::cout, s);

則在std中尋找一個能比對這個函數聲明的函數

operator<<(std::ostream&, std::string)

可以用圓括号來避開ADL如下

(f)(x, y);  // ADL suppressed

To clarify and summarize: name lookup is done at the point of instantiation if the name is dependent, except that for unqualified dependent names the normal name lookup is also attempted early, at the point of definition. All non-dependent names in templates are looked up early, at the time the template definition is parsed. (If necessary, another lookup occurs at instantiation time, when the type of the actual argument is known.)

關聯名稱,在定義時是不确定的,隻能在執行個體化時。

10、友員和模闆

友員模闆提前聲明。可以在主類模闆中完全的定義友員函數。友員模闆也可以出現在非模闆類中。

template<class T> class Friendly {

  T t;

public:

  Friendly(const T& theT) : t(theT) {}

  friend void f(const Friendly<T>& fo) {

    cout << fo.t << endl;

  }

  void g() { f(*this); }

};

11、習語

(1)特征

The traits template technique, is a means of bundling type-dependent declarations together. In essence, using traits you can “mix and match” certain types and values with contexts that use them in a flexible manner, while keeping your code readable and maintainable.

    如char_traits等。

template<class charT,

  class traits = char_traits<charT>,

  class allocator = allocator<charT> >

  class basic_string;

Using traits provides two key advantages: (1) it allows flexibility and extensibility in pairing objects with associated attributes or functionality, and (2) it keeps template parameter lists small and readable. 

(2)政策

(3)遞歸模闆模式

class Counted {

  static int count;

public:

  Counted() { ++count; }

  Counted(const Counted&) { ++count; }

  ~Counted() { --count; }

  static int getCount() { return count; }

};

int Counted::count = 0;

class CountedClass : public Counted {};

class CountedClass2 : public Counted {};

12、模闆元程式設計

#include <iostream>

using namespace std;

template<int n> struct Factorial {

  enum { val = Factorial<n-1>::val * n };

};

template<> struct Factorial<0> {

  enum { val = 1 };

};

int main() {

  cout << Factorial<12>::val << endl; // 479001600

} ///:~

有編譯時程式設計(循環,分解,選擇,斷言等),表達式模闆。關于這一部分,可以參見:

1、C++ Templates,David Vandevoorde.

2、C++程式設計思想,thinking in C++

13、模闆編譯模型

如包含模型(顯式執行個體化,導出模闆)

分離模型(export).

一個模闆意味着一個接口。

繼續閱讀