zoukankan      html  css  js  c++  java
  • 四、设计与声明--条款18-20

    概述

    • 本章主要介绍良好的C++接口的设计和声明。
    • 让接口容易被正确使用,不容易被误用。

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

    假如我们设计了以下代码:

    class Date
    {
    public:
        Date(int month, int day, int year);
        ...
    };
    

    初看此接口也通情达理,年月日都有了。但是客户端经常会出现错误:

    Date(30,3,1997);  // 月份和日期反了
    

    遗憾的是,即使这样调用编译还是不会报错。(就个人见到的来说,会在构造函数中做判断处理,诸如限定年月日的数值大小,但是作者在这本书中介绍的是编写良好的接口,使得客户端能够正确使用这个接口。)

    我们可以考虑将参数封装成不同的类型:

    struct Day
    {
        explicit Day(int day)
        :val(day)
        {
            
        }
        int val;
    }
    

    就像这样,month和year也可以封装成此结构体。与此同时,我们可以将Data构造函数声明成这样:

    Date(const Month& month, const Day& day, const Year& year);
    

    这样的话三个参数都是不同的类型,客户端就不会错误的调用而不报编译错误。

    诸如此类。

    作者总结

    好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
    “促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
    “阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
    shared_ptr支持定制删除器。这可防范DLL问题,可用来自动解除互斥锁等等。

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

    对于设计一个class,作者提出了一下设计规范:

    • 新类型的对象应该如何被创建和销毁。 这是关于new和delete时候需要分配和释放多少,哪些内存来考虑的。
    • 对象的初始化和对象的赋值有什么样的差别? 不会混淆“初始化”和“赋值”操作,以及他们的效率。
    • 新type的对象如果被pass by value意味着什么? 这就是copy构造函数的编写。
    • 什么事新type的“合法值”? 对于类中成员还要进行必要的约束,不是每个值都是合法的。要进行错误检查,异常检测等。
    • 新type需要配合某个继承图系吗? 要注意virtual和non-virtual,尤其是析构函数。
    • 新type需要什么样的转换? 判断隐式转换是否必要,显式转换又是否必要?explicit和non-explicit的使用。
    • 什么样的操作符和函数对此新type而言是合理的? 将合适的访问声明为member函数。
    • 谁该取用新type的成员? 考虑好哪些变量作为public,哪些作为private,哪些作为protected.
    • 什么是新type的“未声明接口”?
    • 你的新type有多么一般化? 可能你的type是一整个type家族,那就定义一个class template吧。
    • 是否真的需要一个新type? 很可能你只需要一个non-menber函数或template就可以达到新目标。

    作者总结

    class的设计就是type的设计。在定义一个新的type之前,请确定你已经考虑过本条款所覆盖的所有讨论主题。

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

    Frist Of All,我们要明确一点:

    pass-by-value操作需要对象调用copy构造函数,这个操作也许(很可能)非常的费时。

    还是老样子,用一段代码来分析:

    class Person
    {
    public:
        Person();
        virtual ~Person();
        ...
    private:
        string name;
        string address;
    }
    //继承上类
    class Student : public Person
    {
    public:
        Student();
        ~Student();
    private:
        string schoolName;
        string schoolAddress;
    }
    

    现在有一个接口:

    bool validateStudent(Student s);
    

    接下来我们调用这个接口:

    Student plato;
    bool isPlatoOK = validateStudent(plato);
    

    好了,背景已经阐述完毕。上述我们已经构建了一段代码,并且提供了一个接口,这个接口参数采用的是pass-by-value方法。那这个接口调用的时候会发生哪些函数调用呢?

    (1) Student的copy构造函数会被调用。返回时销毁s,会调用Student的析构函数。(所以参数的传递成本是一次析构函数和一次copy构造函数的调用)。

    (2) Student里面创建了两个string,调用了两个string的默认构造函数。随后销毁的时候,会调用两次析构函数。

    (3) 构造Student之前会构造基类Person,因此还需要调用基类构造函数。 基类中还有两个string类型成员变量,就再调用了2次string的默认构造函数。销毁时也会调用两次析构函数。

    综合以上三点,如果我们用pass-by-value,就会发生六次构造函数,六次析构函数!

    而我们以pass-by-reference传递的时候,没有任何的构造函数或析构函数被调用,因为没有任何的新对象被创建。

    上面说的是效率上的问题,但不仅仅如此,使用pass-by-reference还可以解决对象切割(slicing) 问题。

    对象切割

    书里翻译的偏向晦涩一些,我用自己的理解+代码来帮助理解一下:

    当一个derive class对象以pass-by-value方式传递给一个base class对象时,base class对象的拷贝构造函数会被调用,derive class对象仅仅留下base class对象成分,derive部分的性质被完全切割掉了。

    可以用以下代码理解:

    // 基类window
    class Window
    {
    public:
        ...
        string name()const;
        virtual void display()const;
    };
    // 子类WindowWithScrollBars
    class WindowWithScrollBars
    {
     public:
     ...
     virtual void display() const;
    };
    

    现在我们有个打印窗口名称的函数:

    void PrintWindowName(Window w)
    {
        cout<<w.name();
        w.display();
    }
    

    如果我们传递的是一个derive对象:

    WindowWithScrollBars wwsb;
    PrintWindowName(wwsb);
    

    很明显,当我们用这种pass-by-value的方式调用的时候,Window类的拷贝构造函数将被调用(这样derive对象就没有被拷贝构造!),也就导致了derive对象的特性化部分完全被切割了,所以在这里无论怎么调用,都只会执行Window类的display函数!

    内置类型是否都适合采用pass-by-value方法?

    不一定。第一大原因是内置类型虽小,但是把它放进一个类中,某些编译器会拒绝将之放入缓存器内,而如果是“光秃秃”的内置类型(即不放在类中),那么编译器将会很乐意放进缓存器内。

    假如是前者,那么使用pass-by-reference是更好的,因为引用底层是用指针来实现的,所以放进缓存器内是绝无问题的。

    还有一个原因是因为虽然现在是内置类型,不排除以后扩大,变成一个复杂的用户自定义类型。为长远考虑,使用pass-by-reference为佳。

    作者总结

    尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可以避免切割问题。

    以上规则并不适用于内置类型,以及STL迭代器和函数对象。对它们而言,pass-by-value往往比较适当。

  • 相关阅读:
    Easy | LeetCode 154 | 剑指 Offer 11. 旋转数组的最小数字 | 二分法
    Easy | 剑指 Offer 17. 打印从1到最大的n位数 | 大数
    Easy | LeetCode 191 | 剑指 Offer 15. 二进制中1的个数 | 位运算
    Easy | 剑指 Offer 57
    Medium | LeetCode 15. 三数之和 | 双指针法
    jq load()方法实现html 模块化。
    vue 剪切图片上传头像,使用 cropperjs 实现
    vue 的nextTick的理解
    使用nodejs进行开发,concurrently模块,使我们同时执行多个命令。
    cookie生命周期expires 时间过期,但是cookie没有被浏览器清理的问题
  • 原文地址:https://www.cnblogs.com/love-jelly-pig/p/9662269.html
Copyright © 2011-2022 走看看