Effective C++读书笔记 Part1

##Effective C++ Notes

### Part I. Accustoming Yourself to C++

####1. View C++ as a federation of languages
- C++ is a multi-paradigm programming language with paradigms including procedure oriented, object oriented and generic programming with their own rules. Which leads to the complexity of such language.

####2 Perfer consts, enums and inlines to \#defines.

- \#define is not counted as a part of the language itself, thus anything defined by \#define is not visible to the compiler, things defined by \#define won‘t be in the symbol table, in another way. Which will lead to difficulties on debugging and refactoring. The better way is to take over \#define by const decoration.

- definition of non-internal type const class member is not allowed in class declaration.

~~~c++
class A
{
private:
static const int member = 1; //OK
static const string str = "Hello"; //Not available
}
const A::string str = "Hello"; //Real definition in .cc file
~~~

- enum hack to achieve const static class members

~~~c++
class A{
private:
enum { cnt = 10 };
int People[cnt];
}
~~~

- pitfalls of \#define
- preprocessor don‘t do type check, which means misused macros may lead to compile time error
- if parameters of a macro is not protected by appropriate brackets, operator priority may leads to unpredicted result

~~~c++
#define mul(a,b) a*b
~~~
mul(1+2, 3+4) will be reaplace by 1+2*3+4 which is clearly what we want.
- Even correctly protected macro parameters may crash

~~~c++
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
~~~
define a = 5, then CALL\_WITH\_MAX(++a,0) will perform f(++a) thus a is incremented twice, but CALL\_WITH\_MAX(++a, 10) will perform f(10) thus a is incremeted once. It is NOT acceptable that A macro‘s behavior is realted to its parameters which may lead to misreable effects

- To avoid these pitfalls we can use template inline functions which is as efficiency as macros but much more safer.

####3. Use const, enum and inline instead of #define, Use const whenever possible

- const decoration behaves based on some complex rules espacially when combined with pointers

~~~c
const char* str = "Hello World"; //const string, non-const pointer
char const* str = "Hello World"; //const string, non-const pointer
char* const str = "Hello World"; //const pointer, non-const string
const char* const = "Hello World"; //const pointer, const string
~~~

- Returning a const may reduce the possibility that the client mess things up via the compiler, a compile time error ins always better than a runtime error

~~~c++
class A;
const A& operator*(const A& lhs, const A& rhs) {...}
A a, b;
a * b == a; //OK
a * b = a; //Compile time ERROR, const instance can‘t be changed
~~~

- const member functions indicates that the function won‘t change the class members **BITWISELY**, thus the function can be performed on a const object
- if a class need a const member function and a non-const overload of such function, the non-const version should be implemented by calling the const version and then perform the const cast on it in order to avoid duplicated codes with garuantee on const properties

~~~c++
class Text
{
public:
const char& operator[](size_t pos)
{ return text_[pos]; }
char& operator[](size_t pos)
{
return const_cast<char>(c
static_cast<const Text&>(*this)[pos]);
}
private:
string text_;
};
~~~
the const version garantees no changes on the object, so it is safe to implement the non-const version function via it, nevertheless it‘s not appropriate to do it in a reversed way since non-const function doesn‘t has such garantees

- bitwise constness is garanteed by the compiler but what we need is conceptual constness indeed, mutable declaration will do that

####4. Make sure that objects are initialized before they‘re used

- whether a variable is initialized by default is not clear in C++, which may lead to unspecified and unpredictable behavior. The rule of default initialization is very complex.
- For the internal types, the C part and non-C part of C++ follow different rules of initialization which resulted in the complexity, thus, it is a good approach to manually initialize a variable before using it in order to guarantee a correct initialization.
- For user-defined types, constructors should provide such guarantee, **Ensure every class member is initialized via every single constructor**.

- Assignment and initialization are different concept in constructor

~~~c++
class Contact
{
public:
Contact(string name, int age, string number)
: name_(name), age_(age), phoneNum_(number) {} //This is initialization
Contact(string name, int age, string number) //This is assignment
{
name_ = name;
age_ = age;
phoneNum = number;
}
private:
std::string name_;
int age_;
std::string phoneNum_;
}
~~~
initialize list is more efficiency than assignments since member instances are initialized on construction, in this case, copy constructor of member variable name_, age_ and phoneNum_ are called before entering the body of contact constructor. In assignment method, default constructor of member variables are called and later assigned with a new value in the body of contact constructor. But in some cases where a class contains several contructors and there are some internal types as member variables, a pesudo-initialization maybe a better solution in consideration of avoidance of duplicating codes.

- initializing sequence of non-local static object defined in different compile units

static objects keep alive since been constructed to the exit of a program, including global objects, objects defined in namespace scope, objects defined in classes and in file scope. non-local static variables are constructed before entering main(). Thus, sequence of initializing of non-local static variables in different compile units will be unsettled.

**File1:**

~~~c++
//file1
class FileSystem
{
public:
std::size_t numDisks() const;
};
extern FileSystem tfs;
~~~

**File2:**

~~~c++
class Directory
{
public:
Directory(param);
}
Directory::Directory(param)
{
std::size_t disks = tfs.numDisks();
}
~~~

**Somewhere in customer code:**

~~~c++
Directory tmpDir(param);
~~~

No one knows when object tfs is initialized. ISO C++ doesn‘t regulates the timepoint when object is initialized, but different non-local static objects may have dependency which may generate a compile time error. Fortunately, this problem could be solved by a the singleton pattern, by replace the non-local static objects with functions returning a reference to a local static object.

**Revised:**

~~~c++
class FileSystem {...};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
class Directory {...};
Directory tmpDir()
{
static Directory td;
return td;
}
~~~

IMPORTANT: this simple singleton implementation is NOT thread-safe.

#### Conclusions:
1.1 Rules for effective C++ programming vary, depending on the part of C++ you are using

2.1 For simple constants, prefer const objects or enums to #defines

2.2 For function-like macros, prefer inline functions to #defines

3.1 Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member function as a whole

3.2 Compilers enforce bitwise constness, but you should program using logical constness.

3.3 When const and non-const member functions have essentially identical implementations, code deplication can be avoided by having the non-const version call the const version

4.1 Manually initialize objects of built-in type, because C++ only sometimes initializes them itself

4.2 In a constructor, prefer use of the member initialization list to assignment inside the body of constructor. List data members in the initialization list in the same order they‘re declared in the class

4.3 Avoid initialization order problems across translation units by replacing non-local static objects with local static objects

时间: 2024-09-30 05:52:43

Effective C++读书笔记 Part1的相关文章

Effective Objective-C 读书笔记

一本不错的书,给出了52条建议来优化程序的性能,对初学者有不错的指导作用,但是对高级阶段的程序员可能帮助不是很大.这里贴出部分笔记: 第2条: 使用#improt导入头文件会把头文件的内容全部暴露到目标文件中,而且如果两个类之间存在循环引用则会出现编译错误,所以要尽量使用@class进行类声明. 如果需要实现一个协议,则必须#improt这个协议的头文件,所以可以将协议单独定义在一个.h文件当中.如果这个协议是代理模式协议的一部分,即需要与类捆绑使用才有实际意义,则建议定义在类当中,并以类名为前

Effective Java 读书笔记(2创建和销毁对象)

第一章是引言,所以这里不做笔记,总结一下书中第一章的主要内容是向我们解释了这本书所做的事情:指导Java程序员如何编写出清晰.正确.可用.健壮.灵活和可维护的程序. 2.1考虑用静态工厂方法代替构造器 静态工厂方法与构造器相比有四大优势: (1)静态工厂方法有名称,具有适当名称的静态工厂方法易于使用.易于阅读: (2)不必每次在调用它们的时候都创建一个新的对象: (3)可以返回原返回类型的任何子类型的对象: (4)在创建参数化类型实例的时候,它们使代码变得更加简洁. 同时静态工厂方法也有两大缺点

Effective Java读书笔记(4 类和接口)

4.1 使类和成员的可访问性最小化 要区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部数据和其他实现细节.设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰的隔离开来,然后模块之间只通过API进行通信,一个模块不需要知道其他模块内部的工作情况,这个概念被称为信息隐藏或封装,是软件设计的基本原则之一. 4.2 在公有类中使用访问方法而非公有域 坚持面向对象程序设计思想:如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改

Effective Java读书笔记(3对于所有对象都通用的方法)

3.1 覆盖equals时请遵守通用约定 什么时候应该覆盖Object.equals()方法呢? 如果类具有自己特有的"逻辑相等"概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. Object.equals()方法具有自反性.对称性.传递性.一致性和与null比较返回false的特点. 实现高质量equals方法的诀窍: (1)使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true,这

Effective C++读书笔记之十二:复制对象时勿忘其每一个成分

Item 12:Copy all parts of an object 如果你声明自己的copying函数,意思就是告诉编译器你并不喜欢缺省显示中的某些行为.而编译器会对"你自己写出copying函数"做出一种复仇的行为:既然你拒绝它们为你写出copying函数,如果你的代码不完全,它们也不会告诉你.结论很明显:如果你为class添加一个成员变量,你必须同时修改copying函数.如果你忘记,编译器不太可能提醒你. 一下提供一种正确的模版: class Date{...}; class

Effective C++读书笔记之十三:以对象管理资源

Item 13:Use objects to manage resources 假设我们使用一个用来塑膜投资行为的程序库,其中各式各样的投资类型继承自一个root class: class Investment { ... };  //"投资类型"继承体系中的root class 进一步假设,这个程序系通过一个工厂函数(工厂函数会"返回一个base class指针,指向新生成的derived class 对象),供应我们某特定的Investment对象: Investment

&lt;Effective Django&gt;读书笔记

In Django parlance, a project is the final product, and it assembles one or more applications together. Manage.py is a pointer back to Django-admin.py with an environment variable set, pointing to your project as the one to read settings from and ope

《Effective C++ 读书笔记》( 一 )

<Effective C++ 读书笔记> 条款01 : 视C++为一个语言联邦 将C++ 视为一个由相关语言组成的联邦而非单一语言. 我们可以将C ++ 看成四个部分 : 1. C 语言 . C ++ 中的区块 , 语句, 预处理器 , 内置数据类型 , 数组 , 指针等统统来自于C语言. 2. 面向对象的 C ++ . 类( 包括构造函数和析构函数 ) , 封装 , 继承 ,多态 , virtual 函数 ( 动态绑定 ) ... 等等 . 这一部分都是基于面向对象而设计的. 3. Temp

effective C++ 读书笔记 条款06

条款06:若不想使用编译器自动生成的函数,就该明确拒绝: 直接看代码与注释: #include <iostream> using namespace std; class Test { public: Test() { } ~Test() { } /* void TT() { Test t1; Test t2(t1); } */ private: Test(const Test& test); Test& operator = (const Test& test); }