《effective c++》学习笔记(五)
来源:互联网 发布:福州大学郑仕标 知乎 编辑:程序博客网 时间:2024/04/26 15:15
尽可能延后变量定义式出现的时间
对象的声明周期需要经过构造和析构,考虑下面这份代码
void foo(bool flag) { string str; if (flag) { return ; } // something}
当flag是true的时候,实际上我们浪费了一次构造string的时间,因为这个string构造了但是根本没有用到,所以我们最好将变量定义到用到它的前一刻最好。
但对于循环来说,比如这份例子
// 方案1void foo(int n) { Bar b; for (int i = 0;i < n;++i) { b.x = i; // something }}// 方案2void foo(int n ) { for (int i = 0;i < n;++i) { Bar b(i); // something }}
方案1中,需要1次默认构造函数 + n次拷贝函数 + 1次析构函数
方案2中,需要n次拷贝构造函数 + n次析构函数
所以在遇到循环时,需要判断拷贝函数和构造 + 析构函数哪个效率更高,如果是拷贝函数效率更高那么使用方案1,如果是构造 + 析构效率更高,那么使用方案2
- 尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率
尽量少做转型动作
C++中应该尽量少做转型动作,一是为了避免降低效率,二是为了减少代码出错概率,考虑下面这份代码:
class Base {public: void foo() { // something }};class Derived : public Base{public: void foo() { static_cast<Base>(*this).foo(); // 注意这里 }};
上述代码中期望Derived中的foo可以调用Base中的foo,然而事实是:static_cast会生成一个临时的Base对象,然后调用那个临时的Base对象的foo成员,而不是调用this对象中Base部分的foo成员。正确的写法应该是下面这样:
class Base {public: void foo() { // something }};class Derived : public Base{public: void foo() { Base::foo(); }};
- 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作, 试着发展无需转型的替代设计
- 如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内
- 宁可使用c++-style转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌
避免返回handles指向对象的内部成分
考虑下面这份代码:
class Foo {public: int& get_x() { return x; }private: int x;};
很容易可以发现,虽然x的private级别,但可以通过get_x得到这个对象,那么实际上x的级别被提升到public级别了,这个private并没有意义。
对于成员函数,也是同样的道理
- 避免返回handles指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”的可能性降至最低
为“异常安全”而努力是值得的
对于一个函数的异常性,我们有三种方案
- 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态
- 强烈保证:如果异常被抛出,程序状态不改变
- 不抛掷保证:承诺绝不抛出异常
有一个一般化的设计叫做 copy and swap。原则很简单:为你打算修改的对象复制一份副本,然后再那副本上进行修改,最后再进行一次swap。
- 异常安全函数即使发生异常也不会泄漏或允许任何数据结构破坏。这样的函数区分三为三种可能的保障:基本型、强烈性、不抛异常型
- “强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义
- 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者
透彻了解inlining的里里外外
- 将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码问题最小化,使程序的速度提升最大化
- 不要只因为function templates出现在头文件,就将它们声明为inline
将文件间的编译依存关系降至最低
考虑下面两份代码:
class Person { public: Person(const std::string& name,const Date& birthday,const Address& addr); std::string name() const; std::string birthDate() const; std::string address() const; ... private: std::string theName; Date theBirthDate; Address theAddress; }; ============================= namespace std { class string; } class Date; class Address; class Person { public: Person(const std::string& name,const Date& birthday,const Address& addr); std::string name() const; std::string birthDate() const; std::string address() const; ... };
当Date的定义改变时(接口不改变),第一份代码会重新编译,进而依赖第一份代码的文件也会重新编译,而第二份代码则不会重新编译。
所以为了避免编译依赖,可以使用一种叫做pimpl的手法,即声明和实现分开写,比如上面的代码可以定义两个类Person和PersonPIMPL,其中具体实现写在PersonPIMPL中:
class PersonIMPL;class Person {public: string get_name();private: shared_ptr<PersonIMPL> _data;};class PersonIMPL {public: string get_name() { return name; }private: string name;};string Person::get_name() { return _data->get_name();}
这样便可以将声明和定义分开。
当然也可以使用抽象基类来实现接口和实现的分离:
class Person {public: virtual string get_name() = 0; virtual ~Person() { };};class PersonIMPL : public Person {public: string get_name() { return name; }private: string name;};
但这样要求使用的时候必须使用Person的指针或引用才可以。
- 支持“编译依存性最小化“的一般构想是:相依与声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes
- 程序库头文件应以“完全且仅有声明式”形式存在,这种做法不论是否涉及templates都适用
- 《Effective C++》学习笔记(五)
- 《effective c++》学习笔记(五)
- Effective Java学习笔记(五)
- Effective Java 学习笔记 (五)
- Effective C++ 学习笔记(五)
- Effective C++学习笔记(五)
- Effective Java 学习笔记(四、五)
- Effective C++学习笔记(五)
- Effective c++ 学习笔记(五)
- Effective C++(五)
- 《Effective C++》(五)
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记(1)
- 《Effective C++》学习笔记(一)
- 《Effective C++》学习笔记(二)
- leetcode-day1
- Eclipse3.6(Helios)版本应用hibernate tools工具创建映射文件后自动建表
- C++ 标准库
- 一起Talk Android吧(第三十八回:Android中的Fragment三)
- 简单的日志储存
- 《effective c++》学习笔记(五)
- django学习(一) form表单编写简单的注册登录页
- 《数学之美》—— 读后总结
- homebrew安装mysql及修改配置
- CodeForces
- bootstrapTable详解(排序,格式化时间,悬浮)
- 婚庆财务管理系统(增删改查功能)
- 使用java交换两个数——CSDN博客
- 【Android API】Android事件分发机制和滑动冲突