栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Effective C++ 中文版

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Effective C++ 中文版

Effective C++ 中文版 explicit:显式的 implicit: 隐式的 预备知识

1、继承关键字说明:

public:可以被该类中的函数、子类的函数、友元函数访问,也可以由该类的对象访问;protected:可以被该类中的函数、子类的函数、友元函数访问,但不可以由该类的对象访问;private:可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象、访问。 1、让自己习惯C++ 命名习惯:

//lhs:left hand side(左手端)
//rhs:right hand side(右手端)
//指向一个T型的对象的指针一般命名为:pt,意思是“pointer to T"
Widget* pw; //pw="ptr to Widget"
class Airpland;
Airplane* pa; //pa="ptr to Airplane"
条款 02:尽量以const,enum,inline替换#define

    对于单纯常量,最好以const对象或enums替换#defines

    对于形似函数的宏(macros),最好改用inline函数替换#defines

条款 03:尽可能使用const
char greeting[]="hello"; 
char* p=greeting;        //non-const pointer,non-const data
const char* p=greeting;  //non-const pointer,const data
char* const p=greeting;  //const pointer,non-const data
const char* const p=greeting;//const pointer,const pointer

说明:如果关键字const出现在星号左侧,表示被指物是常量;如果出现在星号右侧,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

    当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复
条款 04:确定对象被使用前已先初始化

为内置型对象进行手工初始化,因为C++不保证初始化它们构造函数最好使用成员初值列表,而不要在构造函数内进行赋值操作。初值列表的成员变量,其排列顺序应该和它们在class中的声明次序相同为免除“跨编译单元之间的初始化”问题。请以local static对象替换non-local-static对象。 2、构造、析构、赋值运算 条款 05:了解C++默默编写并调用哪些函数

空类:class Empty { };此时编译器会为它声明一个默认的构造、析构、赋值等函数。好比你写了如下函数:

class Empty {
    public:
    Empty() {...} //default构造函数
    Empty(const Empty& rhs) {...}//copy构造函数
    ~Empty() {...}//析构函数,编译器产生的析构函数都是non-virtual
    Empty& operator=(const Empty& rhs) {...}//copy assignment操作符
}
条款 06:若不想使用编译器自动生成的函数,就应该明确拒绝

为了拒绝编译器自动提供的功能,可将相应的成员函数声明为private,并且不予实现。 条款 07:为多态基类声明virtual析构函数

1、多态中如果基类的析构函数不是虚函数造成的后果:当derived class对象经由一个base class指针被删除时,实际执行时对象的derived成分没有被销毁,只有base class成分会被销毁,因此这就造成了”局部销毁“的现象,形成难以察觉的内存泄漏问题。

解决方法:base class的析构函数声明为虚函数。

polymorphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,他就应该拥有一个virtual析构函数classes的设计目的如果不是作为base classes使用,或者不是为了具备多态性,则就不应该声明virtual析构函数 条款 08:别让异常逃离析构函数

**析构函数绝对不要吐出异常。**如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序如果客户需要对某个函数运行期间抛出的异常做出反应,则class应该提高一个普通函数执行该操作(而不是放在析构函数中执行) 条款 09:绝不在构造和析构函数中调用virtual函数

相关链接:C++不要在构造函数和析构函数中调用虚函数 - 云+社区 - 腾讯云 (tencent.com)

为什么不要在构造函数和析构函数中调用虚函数? - jiayouwyhit - 博客园 (cnblogs.com)

在构造和析构期间不要调用virtual函数,因为此类调用不会下降至derived class问题:为何父类(基类)的析构函数一定也要写成虚函数:唯有这样,当delete一个指向子类对象的父类指针时,才能保证系统能够依次调用子类的析构函数和父类的析构函数,从而保证对象(父指针指向的子对象)内存被正确地释放。 条款 10:令operator=返回一个reference to *this 条款 11:在operator=中处理“自我赋值”

确保当对象自我赋值时,operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序,以及copy-and-swap 条款 12:复制对象时勿忘其每一个成分

Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”不要尝试以某个copying函数实现另一个copying函数。应该将共同部分放进第三个函数中,通过调用实现功能复用。 3、资源管理 条款13:以对象管理资源

为防止资源泄露,请“以对象管理资源”。它们在构造函数中获得资源并在析构函数中释放资源两个常被使用的(Resource Acquisition Is Initialization)RAII classes分别是shared_ptr和auto_ptr。前者通常是比较好的选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向nullptr。 条款 14:在资源管理类中小心copying行为

复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为普遍的RAII class copying行为是:抑制copying、采用引用计数法(reference counting)。 条款 15:在资源管理类中提供对原始资源的访问

APIs往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个“取得其所管理的资源”的方法对原始资源的访问可能经由显式转化或隐式转化。一般而言显示转化比较安全,但隐式转化对客户方便。 条款 16:成对使用new和delete时要采取相同形式

如果你在new表达式中使用[],必须在相应的delete表达式也使用[]。如果你在表达式中不使用[],一定不要在相应的delete表达式中使用[] 条款 17:以独立语句将newed对象置入智能指针

以独立语句将newed对象存储于(置于)智能指针内。如果不这样做,一旦出现异常抛出,则可能造成难以察觉的内存泄露

//以下语句可能会出现内容泄露
processWidget(std::tr1::shared_ptr(new Widget),priority);

//解决方法:使用分离语句
std::tr1::shared_ptr pw(new Widget); //在单独语句内以智能指针存储
processWidget(pw,priority()); //这个调用动作绝不会造成内存泄露
4、设计与声明

“让接口容易被正确使用,不容易被误用”

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

shared_ptr提供的某个构造函数接受两个实参:一个是被管理的指针,另一个是引用次数变为0时将被调用的“删除器”。shared_ptr还有一个很好的性质:它会自动使用它的“每个指针专属的删除器”。“阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁。 条款 19:设计class犹如设计type 条款 20:宁以pass-by-reference-to-const替换pass-by-value

尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题。以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较合适 条款 21:必须返回对象时,别妄想返回其reference

绝不要返回一个指向local stack对象的pointer或reference,或返回一个指向heap-allocated对象的reference,或返回一个指向local static对象而有可能同时需要多个这样对象的reference。 条款 22:将成员变量声明为private

切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。protected并不比public更具封装性。 条款 23:宁以non-member、non-friend替换member函数

这样做的目的是增加封装性、包裹弹性和机能扩充性(它毕竟没有增加“能够访问class内的private成分”的函数数量)将所有便利函数放在多个头文件但隶属同一个命名空间,意味着客户可以轻松扩展这一便利函数。 条款 24:若所有参数都需要类型转换,请为此采用non-member函数 条款 25:考虑写出一个不抛出异常的swap函数

典型的swap函数的实现:

namespace std{
    template   //std::swap的典型实现 
    void swap(T& a,T& b){  //置换a和b的值
        T tmp(a);
        a=b;
        b=tmp;
    }
}

1、 c++只允许对类模板偏特化,不允许对函数模板偏特化。

2、 std是一个特殊的命名空间,它允许全特化任何templete,但是不允许添加任何templete。

关于pimpl类:提供一个member swap,并提供一个non-member swap来调用前者,最后特化std::swap()关于pimpl模板类:创建一个包括类定义的namespace,并在此命名空间之中构建non-member swap。关于调用,针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间修饰符”。 5、实现 条款 26:尽可能延后变量定义式的出现时间

“尽可能延后”的真正意义:你不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延迟这部分定义直到能够给它实参进行初始化为止。例如以下代码是一个比较好的书写规范:

std::string encryptPassword(const std::string& passwordd)
{
    ...                               //检查长度
    std::string encrypted(password);  //通过copy构造函数
    								  //定义并初始化
    encrypt(encrypted);
    return encrypted;    
}
条款 27:尽量少做转型工作

C++提供了四种新式转型:

const_cast(expression); //常被用来将对象的常量性去除cast away the constness
dynamic_cast(expression);//常用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个类型
reinterpret_cast(expression);//意图执行低级转型
static_cast(expression);//用来强迫隐式转换

如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果转型是必须的,试着将其隐藏于某个函数背后。客户可以随时调用该函数而不需将转型放进他们自己的代码中宁可使用C++ style(新式)转型,不要使用旧式转型。前者很容易辨识出来。 条款 28:避免返回handle指向对象内部成分

避免返回handles(包括reference、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数行为像个const,并将发生“虚吊号码牌”(指针指向一个已被销毁的对象)的可能性降到最低。 条款 29:为“异常安全”而努力是值得的

当异常抛出时,带有异常安全性的函数会:1. 不泄露任何资源。2. 不破坏任何数据

异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构遭到破坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型“强烈保证”往往能够以copy-and-swap实现出来。但“强烈保证”并非对所有函数都可实现或具备显示意义函数提供“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全保证”中的最弱者(木桶效应) 条款 30:透彻了解Inlining的里里外外

大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而对所有的virtual函数调用也不进行inlinin(因为virtual函数意味着“等待”,只有在运行期才能确定调用哪个函数)

将大多数inlining限制在小型、被频繁调用的函数身上。着可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化不要因为function templates出现在头文件中就将它们声明为inline。

备注:如果函数不是inline函数,那么它有个优点是当该函数发生修改时,客户端只需要重新连接就好,这远比重新编译整个文件负担少很多

条款 31:将文件间的编译依存关系降至最低

支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否设计templates都适用。 6、继承与面向对象设计 条款 32:确定你的public继承模仿出is-a关系

“public继承”意味着is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象 条款 33:避免遮掩继承而来的名称

如果我们继承base class并加上重载函数,而我们又希望重新定义或覆写(推翻)其中的一部分,那么则必须为那些原本会被遮掩的每一个名称引入一个using声明,防止我们希望继承的函数被遮掩

class Derived:public base{
    public:
    using base::mf1;//让base class内名为mf1和mf3的所有东西
    using base::mf2;//在Derived作用域内都可见(并且Public)
    void mf3();
}

当我们仅需要继承base中某一个特殊函数而非全部函数时(就是说不要要所有重载函数),可以采用转交函数(forwarding function),不能采用using声明,因为using声明会让base中所有相同名称都可见(base也有重载)。这也为那些无法进行using声明的旧编译器提供了一条解决方法。

class Derived:private base{
    public:
    virtual void mmf1()//转交函数
    { base::mf1(int);}//默认成为inline。
}

derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。为了让被遮掩的名称可见,可使用using声明式或转交函数。 条款 34:区分接口继承和实现继承

接口继承和实现继承不同。在public继承下,derived classes总是继承base class的接口pure virtual函数只继承接口impure virtual函数是继承接口和一份缺省实现non-virtual函数是继承接口以及强制性实现继承 条款 35:考虑virtual函数以外的其它选择

使用non-virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式。它以public non-virtual成员函数包裹低访问性(private或protected)的virtual函数。将virtual函数替换为“函数指针成员变量”,这是Strategy设计模式的一种分解表现形式。以tr1::function成员变量替换virtual函数,因而允许使用任何可调用物(callable entity)搭配一个兼容于需求的签名式。这也是Strategy设计模式的某种形式。将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现方法。 条款 36:绝不重新定义继承而来的non-virtual函数

原因:调用一个derived class函数(该函数在base class有一个实现,在derived class内也有一个实现,且它是non-virtual函数)。那么此时声明一个derived class对象,程序调用会出现诡异的一面:

class B{
public:
    void mf(); //non-virtual函数
}
class D:public B{
public: 
    void mf();  //它遮盖了B::mf;
}
D x;
B* pB=&x;
D* pD=&x;
pB->mf(); //调用B::mf
pD->mf(); //调用D::mf
//愿意解释:可以看到,都是同一对象x,但由于指向的指针不一样,导致函数调用不用(而非简单的派生类覆盖了基类)
//就是说:决定因素不在于自身,而在于“指向该对象的指针”当初的声明类型
条款 37:绝不重新定义继承而来的缺省参数值

NVI(non-virtual interface)手法:令base class内的一个public non-virtual函数调用private virtual函数,后者可被derived classes重新定义。绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——我们唯一应该覆写的东西却是动态绑定。 条款 38:通过复合模拟出has-a或“根据某物实现出”

复合(composition)的意义和public继承完全不同在应用域(application domain),复合意味着has-a(有一个)。在实现域(implementation domain),复合意味着is-implemented-in-terms-of(根据某物实现出) 条款 39:明智而审慎地使用private继承

Private继承意味着is-implemented-in-terms of(根据某物实现出)。它通常比复合(composition)的级别低。但当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者来讲很重要 条款 40:明智而审慎地使用多重继承

多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承地需要。virtual继承会增大空间、速度、初始化(赋值)复杂度等成本。如果virtual base classes不带任何数据,将是最具有实用价值的情况。多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相结合。 7、模板与泛型编程 条款 41:了解隐式接口和编译期多态

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/768792.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号