栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Tricky Basics of C++ Templates

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Tricky Basics of C++ Templates

Tricky Basics of C++ Templates
  • 关键字 typename
  • 零初始化
  • 使用 this->
  • 原始数组和字符串字面值的模板
  • 成员模板
  • 变量模板
  • 模板的模板参数
  • 引用

这章将介绍关于模板的一些看起来有点棘手或者诡异的知识点,比如 typename 的其他用途、零初始化、函数模板的字符串字面值参数等等。这些知识点在你常年的模板实践中总会遇到。

关键字 typename

在模板内部,可以使用关键字 typename 指明一个标识符为一个类型。例如:

template
class MyClass {
  public:
    ...
    void foo() {
      typename T::SubType* ptr;
    }
};

使用 typename 告诉编译器 SubType 是类内定义的一个类型。没有关键字 typename 前缀的话,编译器会将 SubType 当作一个没有类型的数据成员,而 T::SubType* ptr 会被认为是一个乘法表达式。

零初始化

对于作用域内的内置类型(int,double,pointer,etc.),没有显示进行初始化赋值时,将会产生一个未定义的值。

void foo()
{
  int x; // x has undefined value
  int* ptr; // ptr points to anywhere (instead of nowhere)
}

对于模板,如果模板类型是内置类型,也有同样的问题。

template
void foo()
{
  T x; // x has undefined value if T is built-in type
}

为了解决这个问题,需要显示地为内置类型调用一个默认构造函数将其初始化为零(or false for bool,or nullptr for pointer)。

template
void foo()
{
  T x{}; // x is zero (or false) if T is a built-in type
   // T x = T();  // before C++11
}

对于类的成员变量也是如此。

template
class MyClass {
  private:
    T x;
  public:
    MyClass() : x{} { // ensures that x is initialized even for built-in types
    // MyClass() : x() { // before C++11
    }
    ...
};

C++11 开始,支持直接对非静态成员变量初始化。

template
class MyClass {
  private:
    T x{}; // zero-initialize x unless otherwise specified
    ...
};
使用 this->

对于派生类模板,调用基类的函数时,不一定是真正使用基类的该函数。例如:

class Base {
  public:
    void bar();
};

template
class Derived : Base {
  public:
    void foo() {
      bar(); // calls external bar() or error
    }
};

一种解决方法是使用基类的作用域限定:

template
class Derived : Base {
  public:
    void foo() {
      Base::bar();
    }
};

另一种方法是使用 this->:

template
class Derived : Base {
  public:
    void foo() {
      this->bar();
    }
};
原始数组和字符串字面值的模板

当传递原始数组或者字符串字面值给模板时需要格外小心。

如果模板参数申明为引用,入参不会发生退化(decay)。比如你传递一个 “hello”,它的类型为 char const[6],但是 “hello!” 的类型是 char const[7],二者不是同一个类型。

#include 

template
const T& max(const T& a, const T& b)
{
  return a < b ? b : a;
}

int main()
{
  ::max("hello", "world");   // OK
  ::max("hello", "world!");  // error
}

如果是值传递,则会发生类型退化。例如字符串字面值会退化成 char const*。但它的缺点是失去了数组长度的信息。

针对原始数组和字符串字面值,可以专门提供模板:

template
bool less (T(&a)[N], T(&b)[M])
{
  for (int i = 0; i
    if (a[i]1, 2, 3};
int y[] = {1, 2, 3, 4, 5};
std::cout << less(x, y);         // true:T = int, N = 3,M = 5
std::cout << less("ab", "abc");  // true:T = const char, N = 3,M = 4

如果只支持字符串字面值,可以将模板参数 T 替换为 const char:

template
bool less (const char(&a)[N], const char(&b)[M])
{
  for (int i = 0; i
    if (a[i] 

对于边界未知的数组,可能需要重载和偏特化:

#include 
template
struct MyClass; // primary template

template
struct MyClass // partial specialization for arrays of known bounds
{
  static void print() { std::cout << "print() for T[" << SZ << "]n"; }
};

template
struct MyClass // partial spec. for references to arrays of known bounds
{
  static void print() { std::cout << "print() for T(&)[" << SZ << "]n"; }
};

template
struct MyClass // partial specialization for arrays of unknown bounds
{
  static void print() { std::cout << "print() for T[]n"; }
};

template
struct MyClass // partial spec. for references to arrays of unknown bounds
{
  static void print() { std::cout << "print() for T(&)[]n"; }
};

template
struct MyClass // partial specialization for pointers
{
  static void print() { std::cout << "print() for T*n"; }
};

template
void foo(int a1[7], int a2[], // pointers by language rules
         int (&a3)[42],       // reference to array of known bound
         int (&x0)[],         // reference to array of unknown bound
         T1 x1,               // passing by value decays
         T2& x2, T3&& x3)     // passing by reference
{
  MyClass::print(); // uses MyClass
  MyClass::print(); // uses MyClass
  MyClass::print(); // uses MyClass
  MyClass::print(); // uses MyClass
  MyClass::print(); // uses MyClass
  MyClass::print(); // uses MyClass
  MyClass::print(); // uses MyClass
}

int main()
{
  int a[42];
  MyClass::print(); // uses MyClass
  extern int x[]; // forward declare array
  MyClass::print(); // uses MyClass
  foo(a, a, a, x, x, x, x);
}

int x[] = {0, 8, 15}; // define forward-declared array

输出为:

print() for T[42]
print() for T[]
print() for T*
print() for T*
print() for T(&)[42]
print() for T(&)[]
print() for T*
print() for T(&)[]
print() for T(&)[]
成员模板

类的成员也可以是模板,包括嵌套类和成员函数。一般情况下,不能用不同类型的类互相赋值。

Stack intStack1, intStack2; // stacks for ints
Stack floatStack;         // stack for floats
...
intStack1 = intStack2;   // OK: stacks have same type
floatStack = intStack1;  // ERROR: stacks have different types

可以定义一个赋值运算符的模板来实现不同类型的赋值(通过合适的类型转换)。

template
class Stack {
  private:
    std::deque elems; // elements
  public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is empty
      return elems.empty();
    }
    // assign stack of elements of type T2
    template
    Stack& operator= (Stack const&);
};

template
template
Stack& Stack::operator= (Stack const& op2)
{
  Stack tmp(op2); // create a copy of the assigned stack
  elems.clear();      // remove existing elements
  while (!tmp.empty()) { // copy all elements
    elems.push_front(tmp.top());
    tmp.pop();
  }
  return *this;
}

为了获取用来赋值的源对象所有成员的访问权限,可以把其他的 stack 实例声明为友元。

template
class Stack {
  private:
    std::deque elems; // elements
  public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is empty
      return elems.empty();
    }
    // assign stack of elements of type T2
    template
    Stack& operator= (Stack const&);
    // to get access to private members of Stack for any type T2:
    template friend class Stack;
};

这样,如下的赋值运算符实现就称为可能:

template
template
Stack& Stack::operator= (Stack const& op2)
{
  elems.clear(); // remove existing elements
  elems.insert(elems.begin(), // insert at the beginning
               op2.elems.begin(), // all elements from op2
               op2.elems.end());
  return *this;
}

如下不同类型的类就可以相互赋值了:

Stack intStack; // stack for ints
Stack floatStack; // stack for floats
...
floatStack = intStack; // OK: stacks have different types,
                       // but int converts to float

当然,v.emplace_front(tmp.top()); 会在编译时候检查类型转换是否合适,否则会编译报错。例如:

Stack stringStack; // stack of strings
Stack floatStack; // stack of floats
...
floatStack = stringStack; // ERROR: std::string doesn’t convert to float

更进一步,你还可以改变内部的赋值的容器实现:

template>
class Stack {
  private:
    Cont elems; // elements
  public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is empty
      return elems.empty();
   }
   
  // assign stack of elements of type T2
  template
  Stack& operator= (Stack const&);
  // to get access to private members of Stack for any type T2:
  template friend class Stack;
};

template
template
Stack&
Stack::operator= (Stack const& op2)
{
  elems.clear(); // remove existing elements
  elems.insert(elems.begin(), // insert at the beginning
               op2.elems.begin(), // all elements from op2
               op2.elems.end());
  return *this;
}

成员函数模板的特化。

成员函数模板也能偏特化或全特化。

#include
class BoolString {
private:
std::string value;
  public:
    BoolString (std::string const& s)
      : value(s) {
    }
    
    template
    T get() const { // get value (converted to T)
      return value;
    }
};

// full specialization for BoolString::getValue<>() for bool
template<>
inline bool BoolString::get() const {
  return value == "true" || value == "1" || value == "on";
}

int main() {
  std::cout << std::boolalpha;
  BoolString s1("hello");
  std::cout << s1.get() << 'n'; // prints hello
  std::cout << s1.get() << 'n'; // prints false
  BoolString s2("on");
  std::cout << s2.get() << 'n'; // prints true
  return 0;
}

.template 标识

调用一个成员模板时,显式限定模板实参有时候是有必要的,使用关键字 template 来确保 < 是模板实参列表的开始。例如:

template
void printBitset (std::bitset const& bs) {
  std::cout << bs.template to_string,
                                     std::allocator>();
}

.template 只需要用于依赖于模板参数的名称之后,这里的 b 依赖于模板参数 N。

泛型lambda和成员模板

C++14 引入的泛型 lambda,其实就是成员模板的简化。例如:

[] (auto x, auto y) {
  return x + y;
}

// 等价如下
class SomeCompilerSpecificName {
  public:
    SomeCompilerSpecificName(); // constructor only callable by compiler
    template
    auto operator() (T1 x, T2 y) const {
      return x + y;
    }
};
变量模板

C++14 开始支持变量模板,也即变量的类型也可以参数化。例如:

template
constexpr T pi{3.1415926535897932385};

和所有模板一样,这个声明不应该出现在函数或局部作用域内。使用如下:

std::cout << pi << 'n';
std::cout << pi << 'n';

可以在不同编译单元中申明和使用变量模板。

// header.hpp:
template T val{}; // zero initialized value

// translation unit 1:
#include "header.hpp"
int main()
{
  val = 42;
  print();
}

// translation unit 2:
#include "header.hpp"
void print()
{
  std::cout << val << 'n'; // OK: prints 42
}

变量模板也可以有默认类型。例如:

template
constexpr T pi = T{3.1415926535897932385};

std::cout << pi<> << 'n'; // outputs a long double
std::cout << pi << 'n'; // outputs a float
std::cout << pi << 'n'; // ERROR

变量模板也支持非类型参数。

#include 
#include 
template
std::array arr{}; // array with N elements, zero-initialized

template
constexpr decltype(N) dval = N; // type of dval depends on passed value

int main()
{
  std::cout << dval<'c'> << 'n'; // N has value 'c' of type char
  arr<10>[0] = 42; // sets first element of global arr
  for (std::size_t i = 0; i < arr<10>.size(); ++i) { // uses values set in arr
    std::cout << arr<10>[i] << 'n';
  }
}

变量模板的一个用法是为类模板成员定义变量。

例如,有一个类:

template
class MyClass {
  public:
    static constexpr int max = 1000;
};

template
int myMax = MyClass::max;

// 可以直接写
auto i = myMax;
// 代替下面的写法
auto i = MyClass::max;

类似的例子:

namespace std {
  template
  class numeric_limits {
    public:
      ...
      static constexpr bool is_signed = false;
      ...
  };
}

template
constexpr bool isSigned = std::numeric_limits::is_signed;

// 可以直接写
isSigned
// 代替下面的写法
std::numeric_limits::is_signed

C++17 标准库就利用了变量模板简化了 type traits 的生成值

namespace std {
  template constexpr bool is_const_v = is_const::value;
}

std::is_const_v       // since C++17
std::is_const::value  // since C++11
模板的模板参数

模板参数自己也可以是一个类模板。例如前面的栈类模板的例子,用模板的模板参数,可以只指定容器类型而不需要指定元素类型。

Stack> vStack; // integer stack that uses a vector
Stack vStack; // integer stack that uses a vector

因此,需要将第二个模板参数指定为模板的模板参数:

template class Cont = std::deque>
class Stack {
 public:
   void push(const T&);
   void pop();
   const T& top() const;
   bool empty() const { return elems.empty(); }
 private:
   Cont elems;
};

C++17 开始可以使用 typename 修饰模板的模板参数中的 Cont。

template typename Cont = std::deque>
class Stack { // ERROR before C++17
  ...
};

由于模板的模板参数中的模板参数没有被使用(Cont 没有用到模板参数 Elem),因而可以简写为:

template class Cont = std::deque>
class Stack {
 ...
};

模板的模板实参匹配

容器还有另一个参数,即内存分配器。

template>
         class Cont = std::deque>
class Stack {
  private:
    Cont elems; // elements
    ...
};

Alloc 没被使用,也可以被省略。最终版本的 Stack 模板实现如下:

#include 
#include 
#include 
template
                 >class Cont = std::deque>
class Stack {
  private:
    Cont elems; // elements
    
  public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is empty
      return elems.empty();
    }
    
   // assign stack of elements of type T2
   template
                    >class Cont2>
  Stack& operator= (Stack const&);
  // to get access to private members of any Stack with elements of type T2:
  templateclass>
  friend class Stack;
};

template class Cont>
void Stack::push (T const& elem)
{
  elems.push_back(elem); // append copy of passed elem
}

template class Cont>
void Stack::pop ()
{
  assert(!elems.empty());
  elems.pop_back(); // remove last element
}

template class Cont>
T const& Stack::top () const
{
  assert(!elems.empty());
  return elems.back(); // return copy of last element
}

template class Cont>
template class Cont2>
Stack&
Stack::operator= (Stack const& op2)
{
  elems.clear(); // remove existing elements
  elems.insert(elems.begin(), // insert at the beginning
               op2.elems.begin(), // all elements from op2
               op2.elems.end());
  return *this;
}

int main()
{
  Stack iStack; // stack of ints
  Stack fStack; // stack of floats
  
  // manipulate int stack
  iStack.push(1);
  iStack.push(2);
  std::cout << "iStack.top(): " << iStack.top() << 'n';
  
  // manipulate float stack:
  fStack.push(3.3);
  std::cout << "fStack.top(): " << fStack.top() << 'n';
  
  // assign stack of different type and manipulate again
  fStack = iStack;
  fStack.push(4.4);
  std::cout << "fStack.top(): " << fStack.top() << 'n';

  // stack for doubless using a vector as an internal container
  Stack vStack;
  vStack.push(5.5);
  vStack.push(6.6);
  std::cout << "vStack.top(): " << vStack.top() << 'n';
  vStack = fStack;
  std::cout << "vStack: ";
  while (! vStack.empty()) {
    std::cout << vStack.top() << ' ';
    vStack.pop();
  }
  std::cout << 'n';
}

输出如下:

iStack.top(): 2
fStack.top(): 3.3
fStack.top(): 4.4
vStack.top(): 6.6
vStack: 4.4 2 1
引用
  • http://www.tmplbook.com
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1000702.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号