zoukankan      html  css  js  c++  java
  • Effective C++ 学习笔记

    条款03:尽可能使用const

    对成员函数的const,有两个流行概念:bitwise constness(又称physical constness)和logical constness。

    bitwise constness(物理上const,二进制位const)

    class CTextBlock {
    public:
        ...
        char& operator[](std::size_t position) const {
            return pText[position];
        }
    private:
        char* pText;
    };
    

    这个函数虽然的确不改变类中任何非静态成员的值,但实际上我们仍然可以用此函数返回的引用修改到该类的对象本身。如

    const CTextBlock cctb("Hello");
    char* pc = &cctb[0];
    *pc = 'J';
    

    如此,虽然我们设置了一个常量对象cctb,并且只调用了它的const成员函数,但是终究还是改变了它的值。

    loginal constness(逻辑上const)

    认可逻辑上const的人们认为一个const成员函数可以修改它处理的对象的某些bits,但只有在客户端侦测不出(不知道发生了这件事)才可以如此。如下的CTextBlock类有可能cache文本区块的长度以便应付询问(快速回答):

    class CTextBlock {
    public:
        ...
        std::size_t length() const;
    private:
        char* pNext;
        std::size_t textLength; // 最近一次计算的文本区块长度
        bool lengthIsValid;     // textLength是否还有效
    };
    std::size_t CTextBlock::length() const {
        if (!lengthIsValid) {
            textLength = std::strlen(pText); // const成员函数内赋值不被允许
            lengthIsValid = true; // const成员函数内赋值不被允许
        }
        return textLength;
    }
    

    然而上面的代码不满足物理上的const,编译器不允许,会编译错误。虽然这两个数据的修改对const CTextBlock对象而言可以接受。解决方法是加mutable(可变的)关键字,它释放掉非静态成员变量的物理const(bitwise constness)约束:

    class CTextBlock {
    public:
        ...
        std::size_t length() const;
    private:
        char* pNext;
        mutable std::size_t textLength; // 最近一次计算的文本区块长度
        mutable bool lengthIsValid;     // textLength是否还有效
    };
    std::size_t CTextBlock::length() const { ... } // 同上
    

    non-const成员函数调用const函数提高代码复用性

    class TextBlock {
    public:
        ...
        const char& operator[](std::size_t position) const {
            ...
            return pText[position];
        }
        char& operator[](std::size_t position) {
            return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
        }
    }
    

    条款13:以对象管理资源

    RAII

    “以对象管理资源”(如智能指针)的观念常被称为:RAII(Resource Acquisition Is Initialzation,资源取得时机便是初始化时机)。

    RAII对象们在构造函数中获得资源,在析构函数中释放资源。

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

    C++只允许对类模板偏特化,不允许对函数模板的偏特化

    如:

    template <typename T>
    class Widget {
        ...
    };
    
    namespace std {
        template <typename T>
        void swap<Widget<T>>(Widget<T>& a, Widget<T>& b) {
            ...
        }
    }
    

    编译错误信息:

    error: non-type partial specializtion 'swap<Widget<T> >' is not allowed
    

    条款28:避免返回handles指向对象内部成分

    • handles:引用、指针、迭代器(可以用来取得某个对象)
    • 对象内部成分:成员变量、不被公开使用的成员函数。

    避免返回成员变量的非const引用

    会导致可以通过const成员函数修改到对象内部,见条款3的例子。避免返回成员变量的非const引用,帮助const成员函数的行为像个const。

    避免返回成员变量的const引用

    class Point { ... };
    struct RectData {
        Point ulhc; // upper left-hand corner
        Point lrhc; // lower right-hand corner
    };
    class Rectangle {
    public:
        ...
        const Point& upperLeft() const { return pData->ulhc; }
        const Point& lowerRight() const { return pData->lrhc; }
        ...
    private:
        std::shared_ptr<RectData> pData;
    }
    class GUIObject { ... };
    const Rectangle boundingBox(const GUIObject& obj);
    
    // 用户有可能如下使用:
    
    GUIObject* pgo;
    ...
    const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
    

    然而在最后一行语句结束之后,boundingBox函数返回的const Rectangle临时对象已经被析构。所以这个指针就是一个空悬指针。

    避免返回不被公开使用的成员函数

    不被公开使用的成员函数也就是被声明为protected或private者。如果某成员函数返回不被公开使用的成员函数,那么后者的实际访问级别就会提高至与前者同级。

    条款29:为“异常安全”而努力是值得的

    异常安全函数的三种保证:基本承诺、强烈保证、不抛异常保证

    • 异常安全函数(Exception-save functions)即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本承诺、强烈保证、不抛异常保证
      • 基本承诺(basic guarantee):如果异常被抛出,程序内的任何事物仍然保持在有效状态下。(但有可能处于任何状态——只要是个合法状态)
      • 强烈保证(strong guarantee):如果异常被抛出,程序状态不改变。也就是说如果函数成功,就完全成功;如果函数失败,则程序会恢复到调用之前的状态。
      • 不抛异常保证(nothrow guarantee):承诺绝不抛出异常,因为它们总是可以完成它们原先承诺的功能。
    • “强烈保证”往往能够以 copy-and-swap 实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义(比如完成它们的代价太高)
    • 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。(除非这个函数做了一些措施来提高调用的函数的异常安全保证,比如通过备份把基本承诺变为了强烈保证)

    条款34:区分接口继承和实现继承

    声明纯虚函数、非纯虚函数、非虚函数的目的

    • 声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
    • 声明简朴的(非纯)impure virtual函数的目的是让derived class继承该函数的接口和缺省实现
    • 声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现

    “pure virtual函数必须在derived classes中重新声明,但它们也可以拥有自己的实现”:通过对一个pure virtual函数一份定义:

    class AirPlane {
    public:
        virtual void fly(const Airport& destination) = 0;
        ...
    };
    
    void AirPlane::fly(const Airport& destination) {
        ...
    }
    
    class ModelA: public AirPlane {
    public:
        virtual void fly(const Airport& destination) {
            AirPlane::fly(destination);
        }
        ...
    }
    
    class ModelC: public AirPlane {
    public:
        virtual void fly(const Airport& destination) {
            自己的实现...
        }
        ...
    }
    

    条款46:需要类型转换时请为模板定义非成员函数

    当编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template”内部的friend函数。

    条款47:请使用traits classes表现类型信息

    为什么不把这些信息直接放在对应的类里呢?

    “traits必须能够施行于内置类型”意味“类型内的嵌套信息(nesting information)”这种东西出局了,因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。

    条款48:认识template元编程(TMP)

    Template metaprogramming(TMP,模板元编程)可将工作从运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。

    #include <iostream>
    
    template<unsigned n>
    struct Factorial {
        enum { value = n * Factorial<n-1>::value };
    };
    
    template<>
    struct Factorial<0> {
        enum { value = 1 };
    };
    
    int main() {
        std::cout << Factorial<10>::value; // 3628800
        return 0;
    }
    
  • 相关阅读:
    CookieUtil.java
    观察者模式
    ELK日志分析系统
    Zookeeper安装配置及简单使用
    Zookeeper
    MBMD(MobileNet-based tracking by detection algorithm)作者答疑
    python代码迷之错误(ModuleNotFoundError: No module named 'caffe.proto')
    深度学习中易混概念小结
    Python爬虫小结
    VOT工具操作指南(踩过的坑)
  • 原文地址:https://www.cnblogs.com/sandychn/p/12452472.html
Copyright © 2011-2022 走看看