Effective C++ 学习笔记

别等时光非礼了梦想. 提交于 2020-03-08 13:46:07

1. 基础部分

Item 1: View C++ as a federation of languages.

对于内建类型,按值传递优于按引用传递,对于自定义类型则相反。

C++可视为4中子语言的联合:C,Object-Oriented C++,Template C++和STL。

 

Item 2: Prefer consts, enums, and inlines to #defines.

只有整型常量可以在类声明里面初始化:

class Klass
{
    const static int num=1;//也可只声明,在定义文件中,即类外部定义、初始化;
};

enum类型也可以在类内部初始化。

Item 3: Use const whenever possible.

对于指针p,const在*左边时,p指向的值为常量,const出现在*右边时,p指向的地址为常量。

const int * p1;
int const * p2;
int * const p3;

//以上代码,p1和p2一样,其指向的值为常量;p3指向的地址为常量

如果类Klass有同名的两个成员函数foo,其中一个为const类型,那么const实列优先调用const版的foo。

const函数不能改变非static的成员变量。

mutable修饰的成员,在const函数中也可以改变其值。

在const和非const函数中避免重复:用非const版调用const版。

从非const类型转换为const类型是安全的:static_cast<const Klass & >(kObj)。

Item4: Make sure that objects are initialized before they're used.

C++规定,类的非内建类型(类类型)成员在进入构造函数之前初始化。如果在构造函数中对对象成员赋值,那么在进入构造函数之前,该对象已经通过其对应的类的默认构造函数初始化。所以,构造函数的实现应该尽量使用成员初始化列表。

类的数据成员是const类型或者引用类型,则必须放在初始化列表中。

函数里面定义的static对象,称作本地静态对象。非本地静态对象(Non-local static object)的初始化顺序是不确定的。将非本地静态对象置于一个对应的函数中,并返回该对象,可以确保其初始化。更明确的说是:非const的static成员对象应该置于对应函数中并返回。

2. 构造函数、析构函数、赋值运算符

Item 5: Know what functions C++ silently writes and calls.

如果定义的类中有引用成员,那么就要自己定义赋值操作符即operator=(...),已经引用了一个对象,无法再给其赋值。

类中有const成员变量的情况也是如此。

Item 6: Explicitly disallow the use of compiler-generated functions you do not want.

将默认版本的赋值构造函数和赋值运算符声明在private里面,并且不定义它们,可以阻止调用默认版本。

在C++11中可以直接在成员函数声明后面添加“= delete”,就可以阻止调用。

Item 7: Declare destructors virtual in polymorphic base classes.

如果一个类会被继承,就应该将其析构函数声明为virtual类型,这样才能实现多态。

string类和所有的STL容器类型,都没有定义virtual析构函数,所以不要继承这些类。

如果一个类不是设计为基类,即不被继承,或者不需要实现多态,那么就不必将其析构函数声明为virtual类型。

Item 8: Prevent exceptions from leaving destructors.

一般来讲,静默处理异常不可取,可能会隐藏重要信息。

会抛出异常的代码不要放在析构函数中。

Item 9: Never call virtual functions during construction or destruction.

构造函数和析构函数中不要调用任何虚函数,也不要调用那些调用虚函数的函数。

Item 10: Have assignment operators return a reference to *this.

Item 11: Handle assignment to self in operator=.

赋值运算符重构时,一定要处理将自己赋值给自己的情况。

Item 12: Copy all parts of an object.

继承类(子类)在定义复制构造函数和赋值运算符时,必须考虑其基类部分。在定义其赋值运算符时,要显示调用其基类的赋值运算符。

DerivedClass& DerivedClass::operator=(const DerivedClass& rhs)
{
    BaseClass::operator=(rhs);//调用基类赋值运算符
    //处理继承类数据...
    
    return *this;
}

3. 资源管理

Item 13: Use objects to manage resources.

使用对象来管理资源(特别是指针),这正是C++11智能指针的思想:通过把指针放在对象中,对象的析构函数释放指针。

如果手动释放资源,那么表明该做法不对,应该使用类似智能指针的方式来释放资源。

Item 14: Think carefully about copying behavior in resource-managing classes.

智能指针只支持堆空间分配的数据,因为智能指针在最后是通过delete释放指针对象。

shared_ptr<>智能指针,还有第二个参数,deleter:shared_ptr<Klass>(p,deleter);

拷贝一个资源管理对象时,应该进行深拷贝。

Item 15: Provide access to raw resources in resource-managing classes.

智能指针就是一种资源管理类,其指向的原始指针就是被管理的资源。

智能指针的get()成员函数返回该智能指针所管理的原始指针。

Item 16: Use the same form in corresponding uses of new and delete.

typedef用来定义数组时和定义函数类型一样比较复杂,下面代码以定义一个包含4个int元素的数组。

typedef int ip_addr[4];//注意[4]放在类型名之后
ip_addr server_ip = {192,168,56,1};

尽量不要对数组使用typedef,以免错用delete释放数组对象。

Item 17: Store newed objects in smart pointers in standalone statements.

不要在给函数传参数时直接定义临时(匿名)的智能指针。应该先定义好智能指针对象,再将该对象传递给函数。

4. 设计和声明

Item 18: Make interfaces easy to use correctly and hard to use incorrectly.

接口设计应该使得犯错很难!

尽量让自定义类型和内建类型行为一致。

与其让客户定义智能指针来管理返回的指针对象,不如直接返回智能指针。

 

Item 19: Treat class design as type design.

设计高效的类,考虑一下问题:

  • 类对象如何创建、销毁?

  • 对象初始化和赋值如何区别?

  • 类对象的按值传递意味着什么?复制构造函数。

  • 类的值有何限制条件?

  • 类是否适用于继承?

  • 类允许进行何种转换?

  • 新类可以定义哪些合理的运算符和函数?

  • 哪些标准函数应该被禁用?可以声明为private,或者在声明后面添加=delete。

  • 谁可以访问新类的成员?

  • 新类的非“未声明接口”有哪些?提供性能、异常安全。

  • 新类的通用性如何?也许可以定义成一个类模板。

  • 新类真的是必要的吗?

Item 20: Prefer pass-by-reference-to-const to pass-by-value.

对于内建类型和STL迭代器、函数对象,pass-by-value是高效的。

Item 21: Don't try to return a reference when you must return an object.

永远不要返回一个指向本地栈的指针或者引用。

Item 22: Declare data members private.

将数据成员声明为private,可以提供统一的访问途径,开发者更自由地定义类。

Item 23: Prefer non-member non-friend functions to member functions.

能访问数据成员的函数越多,封装性越差。

类只提供基础操作,由外部函数、工具类提供便利、组合操作。

Item 24: Declare non-member functions when type covnersions should apply to all parameters.

如果能通过类的公共接口实现,尽量避免使用友元函数。

Item 25: Consider support for a non-throwing swap.

在自定义函数中,声明“using std::swap;”,编译器会先查找函数所在命名空间中自定义的具体化的swap函数,如果没有找到,则使用std中定义的swap函数模板。

如果默认的swap函数模板可以满足需要,就用默认的swap。否则,定义一个public成员函数版的swap,再定义一个non-member版的swap。non-member版的swap调用成员函数版的swap。

5. 实现

Item 26: Postpone variable definitions as long as possible.

尽可能推迟定义变量,最好在需要初始化时定义,即定义时就立刻初始化。

Item 27: Minimizing casting.

const_cast<T>(obj):去除obj的const属性,obj应该时一个指针(地址)或者引用,对应用于接收const_cast的也应该是指针或者引用类型,这样才能就地修改obj的值。只有const_cast可以移除const属性。

//Point为自定义类
void castPoint(const Point& pp)
{
    //const对象只能调用const函数,所以getX()原型后面应该有const;
    //const int Point::getX() const;
    cout << "Before cast:(" << pp.getX() << "," << pp.getY() << ")" << endl;
    Point& pp2=const_cast<Point&>(pp);
    pp2.setX(100);                    
    //或者
    //Point* pp2=const_cast<Point*>(&pp);
    //pp2->setX(100);
    
    //或者
    //const_cast<Point&>(pp).setX(100);
    cout << "After cast:(" << pp.getX() << "," << pp.getY() << ")" << endl;
}

迭代器本事就是一个指针,要取其所指向的值,必须解引用(dereference),即*iter。

尽量避免使用cast,必须使用cast的情况下,应尽量将cast置于函数内部。

Item 28: Avoid returning "handles" to object internals.

handle指的是指针、引用、迭代器等。永远不要返回指向类内部成员的handle,否则容易产生悬挂(dangling)handle。

Item 29: Strive for exception-free code.

Item 30: Understand the ins and outs of inlining.

inline函数如果足够短,那么编译后占用空间可能比调用函数还少。

在类声明中定义的函数都是inline函数。

inline函数一般在头文件中定义。大多数编译器不会对复杂的函数进行内联,如包含循环的函数。virtual函数也不会被内联。

Item 31: Minimize compilation dependencies between files.

尽量让头文件不依赖其他文件,以对声明的依赖取代对定义的依赖,如用前向声明和指针。

尽量用指针或者引用,避免使用对象。

通过纯虚函数的方式,定义接口。纯虚函数的实例(指向子类对象)应该以指针或引用的形式出现,因为不可能实例化一个纯虚函数。

6. 继承和面向对象设计

Item 32: Make sure public inheritance models "is-a".

Item 33: Avoid hiding inherited names.

如果基类中函数foo有几个重载版,子类只重新定义了其中一个版本,那么会隐藏基类中其他的重载版本。

//子类中用 using,可以避免隐藏父类的name
using Base::name1;
using Base::name2;

Item 34: Differentiate between inheritance of interface and inheritance of implementation.

纯虚函数可以有定义,但是子类必须重载(override)该函数,唯一调用该纯虚函数的方法是通过类名:AbsClass::pureFunction().

如果一个基类的成员函数是非虚函数,该函数在各个子类中应该表现一致,即不应被重载。非虚成员函数的目的就是让子类强制继承一致的实现。

Item 35: Consider alternatives to virtual functions.

模板方法模式:通过一个public非虚函数调用一个private虚函数。

策略模式:strategy pattern,

Item 36: Never redefine an inherited non-virtual function.

Item 37: Never redifine a function's inherited default parameter value.

虚函数如果有默认参数,子类中重新定义该虚函数时,不能改变该默认参数。

Item 38: Model "has-a" or "is-implemented-in-terms-of" throught composition.

Item 39: Use private inheritance judiciously(谨慎地).

私有继承,基类的所有成员在子类中都变为private。

私有继承,其实就是“is-implemented-in-terms-of”,只需要考虑继承基类的实现,基类的接口全忽略。

尽量使用包含(composition),即”has-a“;必须使用时,才使用私有继承。

Item 40: Use multiple inheritance judiciously.

虚继承类的对象比非虚继承类的对象占用空间多一些,访问基类的数据成员速度更慢一些。

7. Templates and Generic Programming

Item 41: Understand implicit interfaces and compile-time polymorphism.

Item 42: Understand the two meanings of typename.

Item 43: Know how to access names in templatized base classes.

在子类模板中,如果要引用父类模板的name时,用this->name、using Base<T>::name声明或者基类限定(Base<T>::name)的方式。

Item 44: Factor parameter-independent code out of templates.

Item 45: Use member function templates to accept "all compatible types".

STL容器的迭代器几乎都是智能指针。

Item 46: Declare non-member functions inside templates when type conversions are desired.

在类模板里面,对于函数参数为模板的名称的,可以省略<T> 部分。

在类内部定义一个非成员函数的唯一方法是“友元”。

Item 47: Use traits classes for information about types.

Item 48: Be ware of template metaprogramming(TMP).

元编程:编写编译期间执行的基于模板的C++程序。

模板元程序(TMP)是在C++编译器内部执行的C++程序。TMP缺点是编译时间长。

8. Customizing new and delete

Item 49: Understand the behavior of the new-handler.

如果new不能满足空间分配的需求,会抛出一个异常。头文件<new>中声明了一个处理该异常的函数.程序中可以使用std::set_new_handler(...)来给new指定处理异常的函数。

//<new>
namespace std{
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
}

Item 50: Understand when it makes sense to replace new and delete.

Item 51: Adhere to convention when writing new and delete.

只有new-handling函数为null的时候,new失败时会抛出异常。

delete一个null指针是安全的。

Item 52: Write placement delete if you write placement new.

定位new运算符。

如果定义了一个包含额外参数的new运算符,那么就应该定义一个包含同样额外参数的delete运算符。这样自定义的这个new运算符分配空间失败时,就可以通过该delete运算符来处理new分配的空间。

定义了一个定位new运算符,就应该定义一个定位delete运算符和一个常规的delete运算符。

9. Miscellany

Item 53: Pay attention to compiler warnings.

Item 54: Familiarize yourself with the standard library, including TR1.

C++不允许王std名称空间添加任何东西。

Item 55: Familiarize yourself with Boost.

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!