# effective_cpp_2 **Repository Path**: giteeliuyou/effective_cpp_2 ## Basic Information - **Project Name**: effective_cpp_2 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-12-07 - **Last Updated**: 2020-12-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # effective cpp ## accustoming yourself to cpp. ### view cpp as a federation of languages. ### prefer consts, enums, and inlines to #define. * 使用常量替换#define ```c++ const double AspectRatio = 1.653; ``` > 定义常量指针(**constant pointers**) 由于常量定义式通常被放在头文件中(以便对不同的文件含入),因此有必要将指针声明为**const**。 ```c++ const char* const AuthorName = "LiuYou"; // 使用std::string会更加合适。 const std::string AuthorName("LiuYou"); ``` > **class**专属常量 为了将常量的作用域(scope)限制于class内,必须让它成为class的一个成员(member)。 而且为了确保此常量至多只有一份实体,你必须让它成为一个static成员。如果每一个对象需要自己的一份实体,就不能将常量定义为static。 ```c++ class GamePlayer { private: static const int NumTurns = 5; int scores[NumTurns]; }; ``` * 使用**enumeration(the enum hack)**代替#define ```c++ class GamePlayer { private: enum { NumTurns = 5; }; int scores[NumTurns]; }; /** * 或者这样 */ class GamePlayer { private: enum class Value { NUM_TURNS = 5 }; // 不知道为什么要进行强转(类型转换) -> 限定作用域的enum枚举成员不进行隐式的向int类型的类型转换。 int scores[static_cast(Value::NUM_TURNS)]; }; ``` > enum hack * **template inline function**替换#define宏函数(macro) ```c++ #define CALL_WITH_MAX(a, b) \ f((a) > (b) ? (a) : (b)) int f(int a) { return a; } template inline void callWithMax(const T& a, const T& b) { f(a > b ? a : b); } ``` ### use const whenever possible. ### make sure that objects are initialized before they're used. * 将内置类型对象(变量)明确加以手工初始化,至于内置类型以外的任何其它东西,初始化责任落在了构造函数上。 * 确保构造函数运用"成员初始值列表"初始化类的成员变量。 * 不同编译单元内定义的**non-local static object**的初始化次序。 * 所谓的编译单元(**translation unit**)是指产出单一目标文件(**single object file**)的那些源码。 基本上它是单一源码文件加上其所含入的头文件(**#include files**)。 C++对定义在不同的编译单元内的**non-local static object**的初始化相对次序并无明确定义。这是有原因的: 决定 它们的初始化次序相当困难,非常困难,根本无解。 幸运的是一个小小的设计便可完全消除这个问题。唯一需要做的是: 将每个**non-local static object**搬到 自己的专属函数内。(该对象在此函数内被声明为static),这个/些函数返回一个**reference**指向它所含的对象。 然后用户调用这个/些函数,而不直接涉及这些对象。这样**non-local static object**被**local static object** 替换了。而且这是一个***Singleton***的一个常见实现手法。以函数调用(返回一个reference指向local static object) 替换直接访问non-local static object。 ## constructor, destructor, and assignment operator. ### know what functions c++ silently writes and calls. ### explicitly disallow the use of compiler-generated functions you do no want. ### declare destructor virtual in polymorphic base classes. * 并非所有的base classes的设计目的都是为了多态用途。例如: std::string和STL container都不被设计作为 base classes使用,更别提多态了。 * 如果base classes需要进行派生,那么一定要将destructor定义为virtual。 ### prevent exceptions from leaving destructor. ### never call virtual functions during construction or destruction. ### have assignment operators return a reference to *this. ### handle assignment to self in operator=. ### copy all parts of an object. 如果你为class新添加一个成员变量,你必须同时修改copying函数。(同时,你也必须修改class的所有构造函数以及任何非标准形式的operator=。 如果你忘记,编译器不太可能提醒你。) 当你编写一个拷贝构造函数和拷贝赋值运算符,请确保 * 复制所有的local成员变量 * 调用所有base classes内的适当的copying函数 * 不能令copy assignment operator调用copy构造函数 * 不能令copy constructor调用copy assignment operator,同样没有意义 * 如果你发现你的copy constructor和copy assignment operator有相似的代码,你可以建立一个新的成员函数给两者调用, 这样的函数往往是private而且常被命名为init。 ## resource management shared_ptr / unique_ptr / weak_ptr ### use objects to manage resource. 将资源放进对象内,当控制流离开一个函数(作用域)(注: 可能会抛出异常或者意外退出。),该对象的析构函数会自动释放那些资源。 依赖cpp的析构函数自动调用机制确保资源被释放。 以对象管理资源的两个关键想法: * 动态分配内存获得资源后立刻放进管理对象中 shared_ptr & make_shared * 管理对象运用析构函数确保资源被释放 shared_ptr 会自动释放内存 reference-counting smart pointer, RCSP提供的行为类似垃圾回收(garbage collection, gc), 不同的是RCSP无法打破环状引用(cycles of references, 例如: 两个其实已经没被使用的对象彼此互指,因而好像还处在“被使用”状态)。 shared_ptr就是一个RCSP。也确实shared_ptr使用非常的广泛。而且还配合unique_ptr和weak_ptr使用非常的好用以及实用!!!! ### think carefully about copying behavior in resource-managing classes. 如果是在堆上的资源可以使用smart pointer manage resource。但是并非所有资源都是heap-based,对于这些资源而言,smart pointer 往往不合适作为资源掌管者(resource handlers)。所以你需要建立自己的资源管理类。 一个一般化问题是每一位RAII class作者一定要面对的: "当一个RAII对象被复制,会发生什么事?"大多数你会选择一下两种可能: * 禁止复制 * 对底层资源祭出"引用计数法"(reference-count) 通常内涵一个shared_ptr的member,RAII classes便可实现出reference-counting copying行为。 * 复制底部资源 复制资源管理对象时,进行的是"深度拷贝"。 * 转移底部资源的拥有权 例如: unique_ptr > 普遍常见的RAII class copying行为是: 抑制copying、施行引用计数法(reference counting)。不过其它的行为也都可能会使用。 ### provide access to raw resources in resource-managing classes. 很多API直接指涉资源,所以只得绕过资源管理对象(resource-managing objects)直接访问原始资源(raw resources)。 现实中很多API往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个"取得其所管理之资源"的方法。 例如: shared_ptr的get()函数,注意shared_ptr的get()函数返回的指针并不需要delete,不然会产生内存泄漏。 对原始资源的访问可以经由显示转换(调用函数)或隐式转换(其实也是调用函数,运算符重载),一般而言显示转换比较安全,但是 隐式转换对客户比较方便。但是"让接口容易被正确使用,不易被误用",所以使用显示转换函数更加的科学正确。例如: shared_ptr的 get函数。 ### use the same form in corresponding uses of new and delete. ### store newed objects in smart pointers in standalone statements. 以独立语句将newed对象存储于(置于)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。 所以在cpp中使用shared_ptr时不使用new而是使用make_shared。在unique_ptr中还是要使用new因为unique_ptr没有提供类似shared_ptr配套的 make_shared函数。所以使用unique_ptr时需要以独立语句将newed对象置于unique_ptr内。 ```c++ // ... int priority(); void processWidget(std::unique_ptr pw, int priority); // ... // 不推荐 processWidget(new Widget, priority()); // 不推荐 processWidget(std::unique_ptr(new Widget), priority()); // 推荐 // 以独立语句用unique_ptr存储newed所得对象 std::unique_ptr pw(new Widget); processWidget(pw, priority()); ``` ## designs and declarations. ### make interfaces easy to use correctly and hard to use incorrectly. cpp在接口之海漂浮。function interface 、 class interface 、 template interface ...... 许多客户端错误可以应为导入新类型而获得预防。在防范"不值得拥有的代码"上,类型系统(type system)是你的 主要同盟国。所以我们可以导入简单的外覆类型(wrapper types)来区分parameter。 令parameter成为成熟且经充分锻炼的classes并封装其内数据 其实就是限制客户开发者的输入。设计这些接口而且只让客户调用这些接口,不能使用除这些接口之外的东西。 就是让接口的不正确使用在编译期间发现错误,即不让使用接口错误或者不合理的代码通过编译,将错误使用接口 留在编译阶段。 预防客户错误的另一个方法是,限制类型内什么事可做,什么事不能做。常见的限制是加上const。 > "阻止误用"的方法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。 > shared_ptr支持定制删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁(mutex)等等。 ### treat class design as type design. ### prefer pass-by-reference-to-const to pass-by-value. 这个规则并不适用于内置类型,以及STL的iterator和function object。对它们而言,pass by value往往比较适当。 ### don't try to return a reference when you must return an object. 在需要返回一个object时,就让函数返回一个对象。没有关系的。不用返回一个reference或者是一个pointer,可能这样还会出错。 绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回 pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。 条款4已经为"在单线程环境中合理返回reference指向一个local static对象"提供了一份设计实例。 ### declare data members private. 每一个成员变量都需要一个getter函数和setter函数毕竟罕见。 ### prefer non-member non-friend functions to member functions. 宁可拿non-member non-friend函数替换member函数。这样做可以增加封装性,包裹弹性(packaging flexibility)和机能扩充性。 ### declare non-member functions when type conversions should apply to all parameters. ### consider support for a non-throwing swap. ## implements ### postpone variables definitions as long as possible. ### minimize casting. ### avoid returning "handles" to object internals. ### strive for exception-safe code. ### understand the ins and outs of inlining. ### minimize compilation dependencies between files.