[More Effective C++ 学习笔记]基础议题和操作符

<基础议题>

       pointer和reference在继承机制下存在两种型别:

  1. 静态型别是指其声明时的型别;
  2. 动态型别是指它们实际所指的对象来决定。

  条款1:仔细区分pointer和references

  没有null references,一个reference必须总是代表某个对象。

  使用reference之前不需测试其有效性。若使用pointer,通常就是测试它是否为null。

  pointers和references的差异

  1. pointers可以被重新赋值,指向另一个对象;
  2. reference却总是指向(代表)它最初获得的那个对象,即使被重新赋值改变的也是原对象的值。

  operator[]返回某种能够被当做assignment赋值对象,则令operator[]返回一个reference。

  条款2:最好使用C++转型操作符

  static_cast:用于强制类型专函,不涉及继承机制的型别执行类型转换;

  const_cast:用来改变表达式中的常量性(constant)或易变性(volatileness);

  dynamic_cast:用来执行继承机制中安全的向下型别或跨系型别转换动作,即base objects->derived objects。若转换失败,返回null指针(转型对象是指针)或exception(转型对象是reference);

  reinterpret_cast:用户函数指针型别转换。

  条款3:绝对不要以多台(polymorphically)方式来处理数组

  继承性质之一:通过指向其基类对象的指针或引用,来操作继承基类的对象。

  数组声明时,向系统申请 numElemt * sizeof(数组中对象类型)大小的内存。而声名类类型对象时,系统给予对象类类型中数据数据大小的内存。类成员函数最终将转换成全局函数。数组中元素移动一位的内存距离是sizeof(数组对象的类型)。

  条款4:非必要不提供default constructor

  classes类缺乏default constructor在两种情况下出现问题:

  1. 产生数组时,系统调用默认构造函数;
  2. 不适用于许多template-based constainer classes。

  <操作符>

  条款5:对定制的型别转换函数保持警觉

  两种函数允许编译器进行隐式转换:单变量constructor和隐式型别转换操作符。其中,单变量constructors是指能以单一自变量成功调用的constructors。隐式型别转换操作符是指关键词operator之后加上一个型别名称。不需指定返回值类型,因为其返回值型别基本上已经表现于函数名称上。

  根本问题:在你从未打算也未预期的情况下,此类函数可能被调用,而其结果可能是不正确、不直观的程序行为,很难调试。

       关于隐式型别转换操作符的解决方法:以功能对等的另一个函数取代型别转换操作符。而单自变量constructors的解决方法之一:利用关键字explicit。若将constructors声明为explicit,编译器便不能因隐式型别转换的需要而调用它们。

       条款6:区别increment/decrement操作符的前置(prefix)和后置(postfix)形式

       在重载操作符increment/decrement时,为了区分前置式和后置式,让后置式有一个int自变量类型,但没有变量名称,如operator++(int);。在调用后置式时,编译器默默地为该int指定一个0值。

       自增和自减操作符的前置式和后置式返回不同的型别:前置式返回一个reference,后置式返回一个const对象。

  为什么后置式返回一个const对象?(设计classes的一条无上宝典就是:一旦有疑惑,试看ints行为如何并遵守之。)是为了防止如下行为的出现:

  int i = 4;

  i++++;  // 错误,但++++i合法

  条款7:千万不要重载&&,||和,操作符

  C++对于真假表达式采用所谓骤死式(即短路求值)评估方式,其含义是一旦该表达式的真假值确定,纵使表达式中还有部分尚未检验,整个评估工作仍然结束。

  条款8:了解各种不同意义的new和delete

  string *ps = new string(“Memory Management”);

  这里使用的new operator是由语言内建的,它总是做下面的两件事,无论如何你不能改变其行为。它的动作分为两方面:

  1. 它分配足够的内存,用于放置某型别的对象;
  2. 它调用constructor,为刚刚分配的内存中的那个对象设置初值。

  operator new表达式

  new operator调用函数operator new执行必要的内存分配动作,你可以在类中重载该函数。函数operator new通常声明为:

  void* operator new(size_t size);

  operator new只负责内存分配,而new operator的责任就是将operator new返回的内存转换为一个对象。

  表达式:string *ps = new string(“Memory Management”);编译器为该语句产生一些代码:

  void *memory = operator new (sizeof (string));   // get raw memory

  call string::string(“Memory Management”) on *memory // initialize objects

  string *ps = static_cast<string *>(memory);              // ps point the new object

  placement new表达式

  placement new表达式是指对一个已存在的内存缓冲区上构筑对象。

       例如:

       

1 Widget *constructWidgetInBuffer(void *buffer, in widgetSize)
2 {
3 
4     return new(buffer) Widget(widgetSize);
5 
6 }

       该函数返回指针,指向一个Widget object,它被构造于传递给此函数的一块内存缓冲区上。因此,placement new的operator new函数如下所示:

       

1 void * operator new(size_t, void *location)
2 {
3     return location;
4 }

       在函数中没有用到size_t参数,之所以不赋予名称,为的是避免编译器发出错误。

       删除(delete)与内存归还(Deallocation)

       函数operator delete之于内奸的delete operator,就好像operator new之于new operator一样。内存释放动作是函数operator delete执行,通常声明如下:

       void operator delete(void *memoryToBeDeallocation);

       因此,

       delete ps;

       在编译器转换之后:

       ps->~string();               // 调用对象的析构函数

       operator delete(ps);        // 释放对象所占用的内存

       但是需要注意的是,placement new是从已存在的内存区域中构筑对象。因此不能直接调用operator delete来释放该函数申请的内存。因此,调用析构函数函数来析构对象,然后将内存返还回去。

       数组的new和delete

       在给数组分配内存时,new operator 调用operator new[],而delete operator调用operator delete[]。

原文地址:https://www.cnblogs.com/life91/p/3002504.html