当前位置: 首页> 娱乐> 影视 > 佛山网站建设电话_长春建站_广西百度seo_短视频seo是什么

佛山网站建设电话_长春建站_广西百度seo_短视频seo是什么

时间:2025/7/16 9:52:05来源:https://blog.csdn.net/m0_74795952/article/details/143311636 浏览次数:0次
佛山网站建设电话_长春建站_广西百度seo_短视频seo是什么

Effective C++ | 读书笔记 (四)

文章目录

  • Effective C++ | 读书笔记 (四)
      • 7.模板与泛型编程
        • 条款41. 了解隐式接口和编译期多态
        • 条款42. 了解typename的双重意义
        • 条款43. 注意处理模版化基类内的名称
        • 条款44. 将与参数无关的代码抽离template(尚且不理解,完了需要在复习)
        • 条款45. 运用成员函数模版接受所有兼容类型
        • 条款46. 需要类型转换时请为模版定义非成员函数
        • 条款47. 请使用traits class表现类型信息
        • 条款48. 认识template元编程
      • 8.定制 new 和 delete
        • 条款49. 了解new-handler的行为
        • 条款50. 了解new和delete的合理替换时机
        • 条款51. 编写new和delete时需固守常规
        • 条款52. new与delete成对出现
      • 9.杂项讨论
        • 条款53. 不要忽略编译器的警告
        • 条款54. 让自己熟悉包括 TR1 在内的标准程序库
        • 条款55. 让自己熟悉 Boost

7.模板与泛型编程

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

隐式接口:函数模板,类型不清楚,对我们来说接口是隐藏的。

显示接口:我们常规的头文件接口声明就是显示接口,明确了返回值,参数。


编译期多态:编译时实例化模板确定哪个重载函数被调用。

运行期多态:运行时哪一个virtual函数该被绑定。

请记住

  • class和template都支持接口和多态。
  • 对class而言接口是显示的。多态则是通过virtual函数发生于运行期。
  • 对template而言,接口是隐式的。多态则通过template实例化和函数重载解析,发生于编译器。
条款42. 了解typename的双重意义

模版声明有两种形式:

  1. typename
  2. class

这里声明模版参数时,它们的意义完全相同。

不过对于typename在模版中除了声明模版参数外还有几处特别的用处要注意!

template<typename C>
void print2nd(const C& container)
{C::const_iterator* x;...
}

这里有个新名词要了解,嵌套从属类型:即属于模版类型C下的类型,形式:C::xxx

**上面对应的就是C::const_iterator,这里是有歧义的,**C::const_iterator是一个类型了还是一个变量了,如果作为类型上面就是定义一个指针x,如果作为变量就是乘x。对于这种嵌套从属类型,编译器一般默认当变量处理。如果要当类型处理就必须在其前面加关键字typename

typename C::const_iterator* x;  // 这样就显示告诉编译器,C::const_iterator是一个自定义类型

另外对于嵌套从属类型前面加typename,有两处特例不能加。即不能出现在基类和成员初始化列表的嵌套从属类型里(除此之外都要加)。

template<typename T>
class Derived : public Base<T>::Nested  // 不能加typename
{public:explicit Derived(int x) : Base<T>::Nested(x)  // 不能加typename{typename Base<T>::Nested temp;   // 这里要加}
}

请记住

  • 声明template参数时,前缀关键字class和typename可互换,意义一样。
  • 请使用关键字typename标识嵌套从属类型,但不得在基类或成员初始化列表内使用。
条款43. 注意处理模版化基类内的名称
template<typename T>
class LoggingMsgSender : public MsgSender<T>  // 模版化基类
{public:...void sendClearMsg(const MsgInfo& info){...sendClear(info);  // 如果这个接口属于基类的,这里也不认识,因为基类是什么这时编译器不知道...}
}

像上面的sendClear接口模版化基类里是否存在,编译器是不确定的,所以这种编译会报错。有下面3种方式解决这种问题,就是明确告诉编译器假设它存在。

  1. 通过this->sendClear(info);调用,假设sendClear在this中。
  2. 调用前加using声明using MsgSender<T>::sendClear;,明确告诉编译器sendClear在模版基类中。
  3. 调用时明白指明,MsgSender<T>::sendClear(info);

请记住

  • 可在派生类模版内通过this->指明基类模版的成员名称(1),或者由一个明白写出的属于基类的修饰符完成(2, 3)
条款44. 将与参数无关的代码抽离template(尚且不理解,完了需要在复习)

**template是一个节省时间和避免代码重复的一个奇方妙法。**不再需要键入20个类似的class而每一个带有15个成员函数,你只需键入一个class template,留给编译器去实例化那20个你需要的相关class和300个函数。(它们只有在被使用时才会实例化)

template虽然给我们提供了方便,但是注意如果使用不当,很容易导致代码膨胀(执行文件变大)。其结果有可能源码看起来合身而整齐,但目标码却不是那么回事。在template代码中,重复是隐藏的,所以你必须训练自己去感受当template被实例化多次时可能发生的重复。

template<typename T, std::size_t n>  // 这里T称为模版的类型参数,n是非类型参数
class SquareMatrix {
public:...void invert();
}// 实例化
SquareMatrix<double, 5> sm1;
sml.invert();SquareMatrix<double, 10> sm2;
sm2.invert();

上面这段模版封装,多次实例化,其中invert也会实例多份,虽然它们二进制实现一样。这就是隐晦的重复代码。

template<typename T>
class SquareMatrixbase {
protected:...void invert(std::size_t matrixSize);...
}template<typename T, std::size_t n>
class SqureMatrix : public SquareMatrixbase<T> {
private:using SquareMatrixBase<T>::invert;...
public:...void invert() {this->invert(n);}
}

把重复逻辑移到基类中,所有模版类共有,这样就减少了代码膨胀了。

本条款想表达的是使用template时要注意多次实例化后可能带来的代码重复,要尽量避免这种重复代码。

请记住

  • template生成多个class和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
  • 因非类型模版参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
  • 因类型参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同二进制实现的代码共享,如放基类中。
条款45. 运用成员函数模版接受所有兼容类型

在C++中,成员函数模板允许我们编写可以接受多种类型参数的成员函数,而不需要为每种类型都显式地重载函数。这就是“运用成员函数模板接受所有兼容类型”这句话的含义。

简单来说,当你有一个类或结构体,并且你希望它的某个成员函数能够接受不同类型的参数(只要这些类型是兼容的,即可以进行相应的操作),你就可以使用成员函数模板来实现这一点。这样做的好处是代码更加简洁、易于维护,并且提高了代码的复用性。

#include <iostream>  
#include <string>  class Print {  
public:  template <typename T>  void print(const T& value) {  std::cout << "Value: " << value << std::endl;  }  
};  int main() {  Print p;  p.print(42);              // 打印整数  p.print(3.14);            // 打印双精度浮点数  p.print(std::string("Hello")); // 打印字符串  return 0;  
}

还想要表达的是我们封装的模版所有操作行为要和普通类保持一致。即隐式行为要一致。如不同类型可隐式相互转换。

template<typename T>
class SmartPrt {public:SmartPrt(const SmartPrt& other);  //正常的copy构造函数,取消编译器自动生成template<typename U>   // 泛化的copy构造函数(成员函数模版),接受不同类型对象转换SmartPrt(const SmartPrt<U>& other) : heldPtr(other.get()){...}T* get() const {return heldPtr;};...private:T* heldPtr;
}

不过注意泛化的成员函数(即成员函数模版)并不会影响编译器自动生成类默认函数规则。所以如果你要完全自定义类行为,默认产生的函数除了泛化版本,对应的正常化版本也要声明。

请记住

  • 请使用成员函数模版生成可接受所有兼容类型的函数。
  • 如果你声明成员函数模版用于泛化copy构造函数或赋值操作符,你还是需要声明对应正常的copy构造函数和赋值操作符函数。
条款46. 需要类型转换时请为模版定义非成员函数

对应条款24,这里只是模版实现。规则一致,但它们写法上有所区别了。

template<typename T>
class Rational {
public:Rational(const T& numerator = 0,const T& denominator = 1);const T numerator() const;const T denominator() const;...
}// 需要隐式转换的接口定义为非成员函数
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs,const Rational<T>& rhs)
{...};// 使用
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2;  // 这里会编译错误,2不能隐式转换

上面只是把24条款示例改为模版实现,然而模版版本是编译不过的,因为编译器并不知道2要转换为什么。编译器推断不了模版的隐式转换。

对于模版我们只能通过friend和inline特性来实现非成员函数的定义。

template<typename T>
class Rational {
public:...// 这里Rational是Rational<T>的简写形式,在类模版内部可以简写。friend const Rational operator*(const Rational& lhs,const Rational& rhs){return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());}
}

这样就可以编译,连接通过了。

请记住

  • 当我们编写一个class template,而它所提供的函数要支持隐式转换时,请将这些函数定义为class template内部的friend函数。
条款47. 请使用traits class表现类型信息

想要了解STL容器可以转到C++ | STL | 侯捷 | 学习笔记_c++ stl-CSDN博客

请记住

  • Traits class 使得类型相关信息在编译器可用。它们以template和template特化完成实现。
  • 整合重载技术后,traits class有可能在编译期对类型执行if…else测试。(重载是编译期确定,if是运行期确定)

例子:
以 iterator_traits 为例介绍如何实现和使用 traits classes。STL 提供了很多的容器、迭代器和算法,其中的 advance 便是一个通用的算法,可以让一个迭代器移动给定距离:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d); // d < 0 就向后移动
STL 迭代器回顾:
struct input_iterator_tag {}; // 输入迭代器,只能向前移动
struct output_iterator_tag {}; // 输出迭代器,只能向前移动
struct forward_iterator_tag: public input_iterator_tag {}; // 稍强的是前向迭代器,可以多次读写它的当前位置
struct bidirectional_iterator_tag: public forward_iterator_tag {}; // 双向迭代器,支持前后移动
struct random_access_iterator_tag: public bidirectional_iterator_tag {}; // 随机访问迭代器,可以支持 +=, -= 等移动操作

回到 advance 上,它的实现取决于 Iter 类型:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d){// 针对 random access 迭代器使用这种方式实现if (iter is a random access iterator) {iter += d;} else {// 其它迭代器使用 ++ 或 -- 实现if (d >= 0) { while (d--) ++iter; }else { while (d++) --iter; }}
}

接下来就是怎么判断 Iter 的类型是否是 random access 迭代器了,也就是需要知道它的类型。这真是需要使用到 Traits classes 的地方。

实现 Traits classes:
用户自定义类型:

template<typename IterT>
struct iterator_traits {// 类型 IterT 的 iterator_category 就是用来标识迭代器的类别typedef typename IterT::iterator_category iterator_category;
};

指针类型:
指针本身就是可以支持随机访问(random access)的,所以我们对指针类型提供一个偏特化版本即可:

template<typename IterT> // template偏特化
struct iterator_traits<IterT*> { // 针对内置指针typedef random_access_iterator_tag iterator_category;
};

advance 实现:

不好的实现方式:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==typeid(std::random_access_iterator_tag))// ...
}

IterTiterator_traits<IterT >::iterator_category 都是可以在编译期间确定的,而 if 判断却要在运行期间核定,这样不仅浪费时间,也会导致代码膨胀。
建议做法是建立一组重载函数(doAdvance),接受不同的类型,原函数(advance)调用这些重载函数。

// 原函数
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {// 调用不同版本的重载函数doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}// 下面是一系列重载函数
// 随机访问迭代器
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) {iter += d;
}// 双向迭代器
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) {if (d >= 0) { while (d--) ++iter; }else { while (d++) --iter; }
}// 输入迭代器
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag) {if (d < 0 ) {throw std::out_of_range("Negative distance");}while (d--) ++iter;
}
条款48. 认识template元编程

47条款的示例就是使用的模版元编程技术,它是一种把运行期的代码转移到编译期完成的技术。这种技术可能永远不会成为主流,但是如果你是一个程序库开发员,那这种技术就是家常便饭了。

通过模版或重载技术,把如if这种运行期的判断转换为编译期重载函数自动匹配。

它有两个特点:

  1. 它让某些事情更容易。如果没有它,那些事情将是困难的,甚至不可能的。
  2. 由于它将工作从运行期转移到编译期。这可更早发现错误,而且更高效、较小的可执行文件、较短的运行期、较少的内存需求。不过它会使编译时间变长。

请记住

  • 模版元编程可将工作由运行期转移到编译期,因而得以实现早期错误发现和更高的执行效率。
  • 模版元编程可被用来生成客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

8.定制 new 和 delete

了解new和delete相关知识可转入侯捷 | C++ | 内存管理 | 学习笔记(一): 第一章节 primitives-CSDN博客,想要了解内存池相关内容可转入侯捷内存管理第二章节或手写SGI STL,nginx内存池项目博客。

条款49. 了解new-handler的行为

new-handler就是当new抛异常之前,它会先调用一个客户指定的错误处理函数。通过set_new_handler标准库函数指定。

// 当new无法分配足够内存时,被调用
void outOfMem()
{std::cerr << "Unable to satisfy request for memory\n";std::abort();
}int main()
{std::set_new_handler(outOfMem);int* pBigDataArray = new int[100000000L];...
}

上面异常处理是全局的,但有时候你可能需要为不同类处理不同异常。

class X {public:static void outOfMem();...
}class Y {public:static void outOfMem();...
}X* p1 = new X;  // 如果X错误,你希望调用X的错误函数
Y* p2 = new Y;	// 如果Y错误,你希望调用Y的错误函数

C++并不支持class的专属new-hander,但也可以通过其它形式自己实现。

// RAII对象,保证new_handler还原
class NewHandlerHolder {
public:explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}~NewHnadlerHolder(){std::set_new_handler(handler);}
private:std::new_handler handler;// 阻止copiyingNewHandlerHolder(const NewHandlerHolder&);NewHandlerHolder& operator=(const NewHandlerHolder&);
}// 声明
template<typename T>
class NewHandlerSupport {
public:static std::new_handler set_new_handler(std::new_handler p) throw();static void* operator new(std::size_t size) throw(std::bad_alloc);...
private:static std::new_handler currentHandler;
}// 实现
template<typename T>
std::new_handler
NewHandlerSupport<T>::set_new_hnadler(std::new_handler p) throw()
{std::new_handler oldHandler = currentHandler;currentHandler = p;return oldHandler;
}template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc)
{// new如果失败,则先会调用currentHandler,然后set_new_handler会返回上一次的handler。// NewHandlerHolder这个RAII对象则在析构时会把上面返回的上一次new_handler设置回去。NewHandlerHolder h(std::set_new_handler(currentHandler));return ::operator new(size);  
}template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;// 使用,只要继承封装的NewHandlerSupport<T>,就能够实现针对类自己的new_handler了。
class Widget : public NewHandlerSupport<Widget> {...
}
Widget::set_new_handler(xxxx); // xxxx是new失败执行的回调函数
Widget* w = new Widget; // 如果失败,先会调用xxxx,然后会还原new_handler回调函数。
123456

请记住

  • set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
  • 让new不抛异常是一个颇为局限的工具,因为它只是保证了内存分配时不抛异常,后续调用构造函数还是可能抛出异常。=> new做了两件事:1. 分配内存 2. 调用类的构造函数。
条款50. 了解new和delete的合理替换时机

什么时候我们需要替换编译器提供的new或delete呢?下面是三个最常见的理由:

  1. **用来检测运用上的错误。**如new的一段内存,delete时失败了导致内存泄漏。又或多次delete导致不确定行为。
  2. **为了提升性能。**编译器默认提供的new/delete是大众的,均衡的,不针对特定场景特定优化。如需要大量申请/释放内存场景(碎片),我们习知的有内存池技术。
  3. **为了收集使用上的统计数据。**统计任何时刻内存分配情况等。

但是要自定义一个合适的new/delete并非易事,如内存对齐(对齐指令执行效率最高),可移植性、线程安全…等等细节。所以我的建议是在你确定要自定义new/delete之前,请先确定你程序瓶颈是否真的由默认new/delete引起,而且现在也有商业产品可以替代编译器自带的内存管理器。或者也有一些开源的产品可以使用,如Boost的Pool就是对于常见的分配大量小型对象很有帮助。

请记住

  • 有许多理由需要写个自定义的new和delete,包括改善性能、对堆区运用错误进行调试、收集堆区使用信息。
条款51. 编写new和delete时需固守常规

上面条款说了什么时候需要自定义new/delete,本节则告诉你写自定义new/delete需要遵守的一般规范。

请记住

  • operator new 1. 应该内含一个无限循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。2. 它也应该有能力处理0字节申请。3. Class的专属版本则还应该处理“比正确大小更大的申请”(被继承后, new 派生对象,这时可以走编译器默认new操作)。
  • operator delete应该在收到null指针时不做任何事情。Class专属版本还应该处理“比正确大小更大的申请”(同上)。
条款52. new与delete成对出现

请记住

  • 当你写一个operator new, 请确定也写出了对应的operator delete。如果没有这样做,你的程序可能会发生隐晦而时断时续的内存泄漏。
  • 当你声明new和delete,请确定不要无意识地(非故意)遮掩了它们的正常版本。

9.杂项讨论

条款53. 不要忽略编译器的警告

记住后期很多无休止调试就是由于你前期没有重视编译警告引起的。尽管一般认为,写出一个在最高警告级别下也无任何警告信息的程序是理想的,然而如果你对某些警告信息有深刻理解,你倒是可以选择忽略它。不管怎样说,在你打发某个警告信息之前,请确定你了解它意图说出的精确意义。这很重要!

请记住

  • 严肃对待编译器发出的警告信息。努力在你的编译器的最高警告级别下争取无任何警告的荣誉。
  • 不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失。
条款54. 让自己熟悉包括 TR1 在内的标准程序库
  • C++ 标准程序库主要由 STL,iostream,locales 组成,并包含 C99 标准程序库。
  • TR1 组件都在 std::tr1:: 命名空间下,以下是组件实例:
  1. 智能指针。
  2. tr1::function,常用于实现回调函数。
  3. tr1::bind,能够做 STL 绑定器 bind1st 和 bind2nd 所做的每一件事,而又更多。
  4. Hash tables,用来实现 sets,multisets,maps 和 multi-maps。
  5. 正则表达式。
  6. Tuples 变量组,这是标准程序库 pair 的升级,pair 只能持有两个对象,而 tr1::tuple 可持有任意个数对象。
  7. tr1::array,本质是个 STL 化的数组,即一个支持成员函数 begin 和 end 的数组。不过它大小固定,并不使用动态内存。
  8. tr1::mem_fn,这是一个语句上构造与成员函数指针(member function pointers)一致的东西。同样容纳并扩充了 C++98 的 mem_funmem_fun_ref 的能力。
  9. tr1::reference_wrapper,一个让引用的行为更像对象的设施。
  10. 随机数生成工具,它大大超越了 rand。
  11. 数学特殊函数,包括 Laguerre 多项式、Bessel 函数、完全椭圆积分,以及更多数学函数。
  12. C99 兼容扩充,这是一大堆函数和模版用来将许多新的 C99 程序库特性带入 C++。
  13. Type traits,一组 traits classes(条款 47),用以提供类型的编译期信息。
  14. tr1::result_of,这是一个用来推导函数调用的返回值类型的模版。

这些实现一般很多实现在boost库中都有!

请记住

  • C++标准程序库的主要功能由STL、iostreams、locales组成。并包含C99标准程序库。
  • TR1添加了智能指针、一般化函数指针、hash-based容器、正则表达式以及另外10个组件的支持。
  • TR1自身只是一份规范。为获得TR1提供的好处,你需要一份实现。一个好的实现来源是Boost。
条款55. 让自己熟悉 Boost
  • Boost 是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的 C++ 程序库开发。
  • Boost 提供许多 TR1 组件的实现品,以及其它许多程序库。
关键字:佛山网站建设电话_长春建站_广西百度seo_短视频seo是什么

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: