effective c++ 笔记 条款18-25

条款18:让接口容易被正确使用,不易误使用

  1. 使用外覆类型(wrapper)提醒调用者传参错误检查,将参数的附加条件限制在类型本身
Data::Data(int month, int day, int year) { ... }

三个参数类型相同的函数容易造成误用

 Date d1(29, 5, 2014);  //调用顺序错乱,应该是 5, 29, 2014
 Date d2(2, 30, 2014);  //传入参数有误,2月没有30号

导入新的类型

struct  Month {
    explicit Month(int mValue):Value(mValue){}
    int Value;
};

struct  Day {
    explicit Day(int mValue):Value(mValue){}
    int Value;
};

struct  Year {
    explicit Year(int mValue):Value(mValue){}
    int Value;
};
Date d2(2, 30, 2014);  //error,类型错误
Date d3(Day(30), Month(2), Year(2014)); //error,类型错误
Date d4(Month(2), Day(30), Year(2014)); //	正确

限制取值

struct Month
{
    enum E_MON{JAN = 1, FEC, MAR, APR, MAY, JUN, JUL, AGU, SEP, OCT, NOV, DEC};
    explicit Month(const E_MON month) : m_month(month) {}
private:
    int m_month;
};
  1. 从语法层面限制调用者不能做的事
    operate*的返回类型上加上const修饰,防止无意的错误赋值if (a * b = c)
  2. 接口应表现出与内置类型的一致性
    如自定义容器的接口在命名上和STL应具备一致性,或者两个对象相乘,最好重载operator*而不是设计名为”multiply”的成员函数。
  3. 从语法层面限制调用者必须做的事
    如条款13在获取资源时返回的是原始指针,如果客户忘记使用智能指针进行管理呢?最好的做法是令获取资源的接口直接返回智能指针。
    对象在动态连接程序库(DLL)被 new 创建,却在另一个 DLL 内被另一个delete销毁。shared_ptr 保证对象会使用原来所在单元的 delete,因此
std::shared_ptr<Investment> createInvestment(){
	std::shared_ptr<Investment> retVal(static_cast<Invertment*>(0), getRidofInvestment)
	//因为0不是指针,需要强转
	//getRidofInvestment函数作为删除器
	retVal = ...; //令retVal指向正确对象
	return retVal;
}

如果被pInv管理的原始指针可以在建立pInv之前确定下来,将其传给pInv的构造函数会比,先将pInv初始化为null再赋值为佳,原因条款26

条款19:设计class犹如设计type

  1. 新 type 对象应该如何被创建和销毁? 类中构造函数、析构函数、内存分配和释放函数(operator new,operator new[],operator delete,operator delete[])操作符的重构需求。
  2. 对象的初始化和赋值该有什么样的差别? 构造函数和赋值操作符的区别,重点在资源管理上
  3. 新 type 的对象如果被按值传递,意味着什么? 拷贝构造函数的实现
  4. 什么是新 type 的合法值? 类中的成员函数必须对类中成员变量的值进行检查,如果不合法就要尽快解决或明确地抛出异常。决定了你的 class 必须维护的约束条件,在语法层面、至少在编译前应对用户做出监督
  5. 你的新 type 需要配合某个继承图系吗? 类是否受到基类设计地束缚,是否拥有该覆写的虚函数,是否允许被继承(若不想要被继承,应该声明为final)
  6. 你的新 type 需要什么样的转换? 新类型和已有类型之间的隐式转换问题,类型转换函数和非explicit函数之间的取舍,编写类型转换函数 operator xxx(),或者显式编写转换函数。条款15
  7. 什么样的操作符和函数对此 type 而言是合理的? 影响到你将为你的类声明哪些函数和重载哪些运算符
  8. 什么样的标准函数应该被驳回? 什么样的标准函数应该被驳回?条款6
  9. 谁该取用新 type 的成员? 类中哪些成员设为 public,private 或 protected,以及友元类和友元函数的设置
  10. 什么是新 type 的“未声明接口”? 为未声明接口提供效率、异常安全性以及资源运用上的保证,并在实现代码中加上相应的约束条件。
  11. 你的新 type 有多么一般化? 如果需要定义一类 types,应该定义一个新的 class template。
  12. 你真的需要一个新 type 吗? 如果只是定义新的 derived class 以便为既有的 base class 添加功能,说不定单纯定义一个或多个 non-member 函数或者 templates,更能达到目标

条款20:宁以 pass-by-reference-to-const 替换 pass-by-value

c++函数默认传值
函数接口应该以const引用的形式传参,而不应该是按值传参。
传值涉及大量参数的复制,这些副本大多是没有必要的
按引用传参也可以避免对象切片(Object slicing) 的问题 (对于多态而言,将父类设计成按值传参,如果传入的是子类对象,仅会对子类对象的父类部分进行拷贝,即部分拷贝,而所有属于子类的特性将被丢弃,造成不可预知的错误,同时虚函数也不会被调用)
小的类型并不意味着按值传参的成本就会小。类型大小与编译器的类型和版本有很大关系,某些类型在特定编译器上编译结果会比其他编译器大得多。小的类型也无法保证在日后始终很小。
只有内置类型,以及STL的选代器和函数对象,传值更合适

条款 21:必须返回对象时,别妄想返回其引用

绝不要返回pointer或reference指向一个local stack对象,局部变量在函数结束时就销毁了
或返回reference指向一个heap-allocated对象,额外控制delete可能出错

w = x * y * z; //重载了operator*,如果引用指向的是堆内存,会内存泄漏

或返回 pointer或 reference指向一个local static对象而有可能同时需要多个这样的对象

对于C++11以上的编译器,可以采用给类型编写“转移构造函数”以及使用std::move()函数更加优雅地消除由于拷贝造成的时间和空间的浪费。
条款4已经为“在单线程环境中合理返回reference指向一个local static对象”提供了一份设计实例。(单例模式)

条款22:将成员变量声明为private

请对class内所有成员变量声明为private,private意味着对变量的封装。
从语法一致性看,所有的变量都是private,那么所有的public和protected成员都是函数了,用户在使用的时候也就无需区分
所有类的使用者想利用私有变量实现自己的业务功能时,就必须通过我们留出的接口,这样的接口便充当了一层缓冲,将类型内部的升级和改动尽可能的对客户不可见——不可见就是不会产生影响
protected 并不比public更具封装性
public、protected和private三者反应的是类设计者对类成员封装特性的不同思路——对成员封装还是不封装,如果不封装是对第一类客户不封装还是对第二类客户不封装。

条款23:宁以non-member, non-friend替换member函数

class WebBrowser {          //  一个浏览器类
public:
    void clearCache();      // 清理缓存,直接接触私有成员
    void clearHistory();    // 清理历史记录,直接接触私有成员
    void clearCookies();    // 清理cookies,直接接触私有成员
    void clearBrowser();           // 在内部调用上边三个函数,不直接接触私有成员,应该放在类外
}

无需直接访问private成员,而只是若干public函数集成而来的member函数。本条款告诉我们:这些函数应该尽可能放到类外。
关于类的封装性:封装的作用是尽可能减小被封装成员的改变对类外代码的影响。某成员封装的好坏:看类内有多少(public或protected)函数直接访问到了这个成员,这样的函数越多,该成员的封装性就越差——该成员的改动对类外代码的影响就可能越大。对于上述clearBrowser函数,设计者本意是不应直接访问任何私有成员,只是公有成员的简单集成(没有必要让其也拥有访问类中private成员的能力),以最大程度维护封装性。但在类的未来维护中,可能忘记设定,在此函数中添加对私有成员的直接访问
成员函数不仅可以访问private成员变量,也可以取用private函数、enums、typedefs等等。而非成员非友元函数能实现更大的封装性,因为它只能访问public函数
关于包括弹性和机能扩展性:提取至类外,通过不同的工具类或者namespace来明确责任,可以从更多维度组织代码结构,并优化编译依赖关系。当我们使用不同功能时就可以include不同的头文件,而不用在面对cache的需求时不可避免的将cookies的工具函数包含进来,降低编译依存性。这也是namespace可以跨文件带来的好处。
命名空间可以跨越多个源码文件而类则不可以。

//在C++中,比较自然的做法是让clearBrowser()函数成为一个non-member函数并且位于WebBrowser类所在的同一个命名空间(namespace)中。
namespace WebBrowserStuff {	
  class WebBrowser {   //核心机能
      public :
          void clearCache();
          void clearHistory();
          void clearCookies();
      };

      // non-member函数,提供几乎所有客户都需要的核心机能
      void clearBrowser(WebBrowser& wb) {
          wb.clearCache();
          wb.clearHistory();
          wb.clearCookies();
      }
}

一个像WebBrowser这样的类中可能有大量的便利函数,如书签便利函数、打印便利函数、cookies管理有关的便利函数.为了防止多个便利函数之间发生编译相互依赖性,分离它们的最直接方法是将书签便利函数声明在一个头文件中,将cookies管理有关的便利函数声明在另一个头文件中,再将打印便利函数声明于第三个头文件中

// 头文件webbrowser.h,这个头文件针对WebBrowser类
namespace WebBrowserStuff{
   class WebBrowser{
       // ...
   };
   // ...   non-member函数
}
// 头文件webbrowserbookmarks.h
namespace WebBrowserStuff{
   // ...   与书签相关的便利函数
}
// 头文件webbrowsercookies.h
namespace WebBrowserStuff{
   // ...   与cookies管理相关的便利函数
}

本条款讨论的是那些不直接接触私有成员的函数,如果你的public(或protected)函数必须直接访问私有成员,那请忘掉这个条款

条款24:若所有参数皆需类型转换,请为此采用non-member函数

这个条款告诉了我们操作符重载被重载为成员函数和非成员函数的区别
如果一个操作符是成员函数,那么它的第一个操作数(即调用对象)不会发生隐式类型转换。
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数,第一个操作数,即调用对象)进行类型转换,那么这个函数必须是个non-member。

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1);
    ...
};
Rational oneHalf(1, 2);
result = oneHalf * 2;    // 正确
result = 2 * oneHalf;    // 报错

等价于

result = oneHalf.operator*(2);    // 正确
result = 2.operator*(oneHalf);    // 报错

应放在类外

const Rational operator*(const Rational& lhs,  Rational& rhs)
 {
     return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
 }

条款25:考虑写出一个不抛异常的swap函数

std::swap函数在 C++11 后改为了用std::move实现,因此几乎已经没有性能的缺陷
原文的思想:

  1. 当std::swap对你的类型效率不高时,提供一个swap成员函数,这个成员函数不抛出异常,只对内置类型进行操作
  2. 如果提供一个member swap,也该提供一个non-member swap来调用前者,对于普通类,也请特化std::swap
  3. 调用swap时,区分是调用自身命名空间的swap还是std的swap,不能乱加std::符号
  4. 为“用户自定义类型”进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

函数匹配优先级:普通函数 > 特化函数 > 模板函数

你可能感兴趣的:(effective,c++,c++,笔记)