Effective C++ 7.0 模板与泛型编程

2024-06-08 13:18

本文主要是介绍Effective C++ 7.0 模板与泛型编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

条款41   了解隐式接口和编译器多态

1. classes 和 templates 都支持 接口 和多态

2. 对 classes 而言接口是 显式 的,以函数签名为中心, 多态则是通过 virtual 函数发生于运行期间。

3. 对 template 参数而言, 接口是隐式的, 奠基于有效的表达式。

   多态则是通过  template具现化 和  函数重载解析 发生于 编译期。
   
   
 条款42   了解typename的双重意义
 1. 声明 template 参数时, 前缀关键字 class 和 typename 可互换。
 
 2. 请使用关键字typename标识嵌套从属类型名称; 但不得在 base class list(基类列) 或 member initialization list(成员初值列)内以它作为base class 修饰类。
 
  template<typename C>
 
  void f(const C& container,         // 不允许使用 typename
         typename C::iterator iter   // 一定要使用 typename 说明C::iterator 是一种数据类型
        );
 

   
   template<typename T>
   class Derived: public Base<T>::Nested      // base class list 中 不允许 typename
   {
   public:
       explicit Derived(int x):Base<T>::Nested(x)  // member initialization list 中 不允许 typename
       {
           // 嵌套从属类型名称  既不在base class list 也不在 mem init list 中
           // 作为一个base class 修饰符 需要加上 typename
           typename  Base<T>::Nested temp;
       }
   
   };
   
   
 条款43: 学习处理模板化基类内的名称
 
 1. 可在 derived class templates 内通过 "this->" 指涉 base class templates 内的成员名称, 或借由一个明白写出
    的"base class 资格修饰符"完成。
 
    采用template的一个算法:
    
    class CompanyA
    {
    public:
    ... ...
    void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
    ... ...
    };
    
    class CompanyB
    {
    public:
    ... ...
    void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
    ... ...
    };
    
    ... ...
    
    class MsgInfo { ... };
    
    template<typename Company>
    class MsgSender
    {
    public:
       ...
       void sendClear(const MsgInfo& info)
       {
         std::string msg;
         Company c;
         c.sendCleartext(msg);
       }
       
       // 类似sendClear, 唯一不同的是 这里调用了 c.sendEncrypted
       void sendSecret(const MsgInfo& info)
       { ... }
    };
    
    
    // 日志记录某些信息
    template<typename Company>
    class LoggingMsgSender:public MsgSender<Company>
    {
    public:
    ... ...
    void sendClearMsg(const MsgInfo& info)
    {
      sendClear(info);   // 调用base class函数,这段代码无法通过编译。
    }
    ... ...
    };
    
    验证:
    当
    class CompanyZ
    {
    public:
    ... ...
    // 这个class 不提供 sendCleartext函数 void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
    ... ...
    };
    
    template<>  // 全特化
    class MsgSender<CompanyZ>
    {
    public:
       ...
       // 类似sendClear, 唯一不同的是 这里调用了 c.sendEncrypted
       void sendSecret(const MsgInfo& info)
       { ... }
    };
    
    此时若在
        // 日志记录某些信息
    template<typename Company>
    class LoggingMsgSender:public MsgSender<Company>
    {
    public:
    ... ...
    void sendClearMsg(const MsgInfo& info)
    {
      sendClear(info);   // 如果Company == CompanyZ 这个函数不存在
                         // 所以上面代码编译不通过是有道理的。
    }
    ... ...
    };
    
    
    为了不让其编译失效,有三种办法:
    (1) 加上 this->
    template<typename Company>
    class LoggingMsgSender:public MsgSender<Company>
    {
    public:
        ... ...
        void sendClearMsg(const MsgInfo& info)
        {
           this->sendClear(info);   // 成立,假设sendClear将被继承
        }
        ... ...
    };
    
    (2)使用using声明式
    template<typename Company>
    class LoggingMsgSender:public MsgSender<Company>
    {
    public:
        using MsgSender<Company>::sendClear;  // 告诉编译器,请他假设sendClear位于base class 内。
        ... ...
        void sendClearMsg(const MsgInfo& info)
        {
           sendClear(info);   // 成立,假设sendClear将被继承
        }
        ... ...
    };
    
    (3)明白指出被调用位于base class 内:
    template<typename Company>
    class LoggingMsgSender:public MsgSender<Company>
    {
    public:
        ... ...
        void sendClearMsg(const MsgInfo& info)
        {
           MsgSender<Company>::sendClear(info);   // 成立,假设sendClear将被继承
        }
        ... ...

    };


条款44:将与参数无关的代码抽离 templates
 
 1. Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依联系

    因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。
    
    举例:
    template<typename T,std::size_t n>
    class SquareMatrix
    {
    public:
       ... ...
       void invert();   // 求逆矩阵
    };
    
    现在考虑这些代码:
    SquareMatrix<double,5> sm1;
    sm1.invert();  // 调用 SquareMatrix<double,5>::invert();
    SquareMatrix<double,10> sm2;
    sm2.invert();  // 调用 SquareMatrix<double,10>::invert();
    这里具现了两份,引起了代码膨胀。
    
    避免代码膨胀
    方法1:
    template<typename T>   // 与尺寸无关的 base class
    class SquareMatrixBase
    {
    protected:
        ... ...
        void invert(std::size_t matrixSize);
        ... ...
    };
    
    template<typename T,std::size_t n>
    class SquareMatrix: private SquareMatrixBase<T>
    {
    private:
      using SquareMatrixBase<T>::invert;
    
    public:
    ... ...
       void invert()  { this->invert(n); }  // 制造一个inline调用   调用base class版的invert.
    }
    
    SquareMatrix<double,5> sm1;
    sm1.invert();  
    SquareMatrix<double,10> sm2;
    sm2.invert();  
    // 由于共享同一份  SquareMatrixBase<double> 则只具现一份代码

    方法2:
    存储一份指针:
    template<typename T>
    class SquareMatrixBase
    {
    protected:
      SquareMatrixBase(std::size_t n, T* pMem): size(n), pData(pMem) { }
      void setDataPtr(T* ptr)  { pData = ptr; }
      ... ...
    private:
       std::size_t size;   // 矩阵的大小
       T* pData;           // 指针, 指向矩阵的内容
    };
    
    // 这允许 derived class 决定内存分配方式。
    某些将矩阵存储在内部
    template<typename T,std::size_t n>
    class SquareMatrix: private SquareMatrixBase<T>
    {
    public:
       SquareMatrix():SquareMatrixBase<T>(n,data) {}
       ...
    
    private:
       T data[n*n];
    };
    
    // 将每一个矩阵的数据放进heap
    template<typename T,std::size_t n>
    class SquareMatrix: private SquareMatrixBase<T>
    {
    public:
       SquareMatrix():SquareMatrixBase<T>(n,0), pData(new T[n*n])
       {
         this->setDataPtr(pData.get());
       }
       ...
    
    private:
       boost::scoped_array<T> pData;
    };

 条款45: 运用成员函数模板接受所有兼容类型
 
 原始指针类型之间的转换是隐式转换,因此并未声明为explicit。
 
 template<typename T>
 class SmartPtr
 {
 public:
   template<typename U>//member template,生成copy构造函数
   SmartPtr( const  SmartPtr<U>&  other): heldPtr(other.get()) { } //暗示只有U*可转为T*才可通过编译
   T*  get( )const { return heldPtr; }
   
private:
   T*  heldPtr;
};

在class内声明泛化copy构造函数并不会阻止编译器生成它们自己的copy构造函数(non-template),
所以如果想要控制copy构造函数的方方面面必须同时声明泛化copy构造函数和正常的copy构造函数。
同理,适用于赋值操作。

template<class  T>
class  shared_ptr
{
public:
    shared_ptr ( shared_ptr const&  r);
    
    template<class  Y>
    shared_ptr( shared_ptr<Y>  const&  r);    

    shared_ptr&  operator = (shared_ptr const&  r);    

    template<class  Y>         
    shared_ptr&  operator = (shared_ptr<Y> const&  r);
};

 1. 请使用 member function templates(成员函数模板) 生成 "可接受所有的兼容类型"的函数
 2. 如果你声明 member templates 用于 "泛华copy构造函数" 或 "泛化assignment操作",
    你还是需要声明正常的 copy构造函数 和 copy assignment 操作符。
 
 
 条款46: 需要类型转换时请为模板定义  非成员函数
 
 template<typename T>

 class Rational
 {
   public:
    Rational(const T& numerator = 0, const T& denominator = 1):n(numerator),d(denominator){}
    const T numerator()   const{return n;}
    const T denominaotr() const{return d;}
    
   private:
     T n, d;
};

template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{
   return Rational<T>(lhs.numerator()*rhs.numerator(), lhs.denominaotr()*rhs.denominaotr());
}

Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf * 2; //编译error

operator*第一参数被声明为Rational<T>,而传给operator*的第一实参的类型是Rational<int>,所以T是int。第二参数是Rational<T>,但传入的实参是整数2。
template实参推导过程中并不考虑通过构造函数而发生的隐式类型转换。因此,不会转换为Rational<int>。


解决方法:
template class内的friend声明式可以指涉某个特定函数。因此可以声明operator*是Rational<T>的一个friend函数。
编译器总是能够在class Rational<T> 具现化时得知T。

template<typename T>
class Rational
{    …

     friend const Rational<T> operator*(const Rational& lhs, const Rational& rhs);   // 模板函数必须放在 .h 文件中 只能声明 即只能放在类里面。

};


template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){ … }
可以通过编译,但不能链接。


编译器知道我们要调用哪个函数,但那个函数只被声明于Rational内,并没有被定义出来。
尽管我们意图令此class外部的operator* template提供定义式,但是行不通。
最简单的方法是:

template<typename T>
class Rational
{ …

    friend const Rational<T> operator*(const Rational& lhs, const Rational& rhs)
    {

       return Rational<T>(lhs.numerator()*rhs.numerator(), lhs.denominaotr()*rhs.denominaotr());

    }

};

为了让类型转换可能发生于所有实参身上,我们需要一个 non-member 函数; 为了让这个函数自动具现化,
我们需要将它声明在class内部。而满足两项的只有一个办法:在class内部声明一个friend函数。

当我们编写一个class template, 而它所提供之“与此template相关的”函数支持"所有函数之隐式类型转换"时,
请将那些函数定义为"class template 内部的 friend函数".
 
 
 条款47:  请使用 traits classes 表现类型信息
 参考<STL源码剖析>内的实现
 
 
 条款48: 认识template元编程
 
  TMP是编写template-based C++程序并执行编译期的过程。TMP是以C++写成,执行于C++编译器内的程序。一旦TMP程序结束执行,其输出也就是templates具现出来的若干C++源码,便会一如往常地被编译。
  TMP(template metaprogramming)模板元编程可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的行为效率。
  TMP是图灵完全的,足以计算任何事物。条款47中的traits就是TMP,traits引发编译期发生于类型身上的if … else计算。

  示例:编译期计算阶乘!

  template<unsigned n>

  struct Factorial{

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

  };

  template<>

  struct Fatorial<0>{

   enum {value = 1};

   };

   std::cout<<Factorial<5>::value<<endl; //输出120

这篇关于Effective C++ 7.0 模板与泛型编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1042290

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

uva 1342 欧拉定理(计算几何模板)

题意: 给几个点,把这几个点用直线连起来,求这些直线把平面分成了几个。 解析: 欧拉定理: 顶点数 + 面数 - 边数= 2。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc