Effective C++笔记(7)—实现

傷城~ 2022-06-12 01:48 212阅读 0赞

尽可能延后变量定义式出现的时间

构造和析构一个对象需要有一定的代价,延后变量定义,可以改善效率。
比如说:

  1. string func(string &s)
  2. {
  3. string t;//方式1.
  4. if(s.size()==0)
  5. return string();
  6. string t;//方式2.
  7. for(int i=0;i<s.size();++i)
  8. t+=(s[i]+1);
  9. return t;
  10. }

如果我们传入一个空串s,在方式1中,定义变量t,那么变量t完全没有被用到,然而却要经历一次构造和析构。

这里只是用到string,如果考虑一个复杂的class类型,默认构造的代价可能更大。

这里面有一个有意思的问题:
变量的定义究竟是放在循环外,还是循环内。
考虑下述两种情况:

  1. //情况A
  2. Foo f;
  3. for(int i=0;i<n;++i)
  4. {
  5. f=取决于i的某个值;
  6. //...
  7. }
  8. //情况B
  9. for(int i=0;i<n;++i)
  10. {
  11. Foo f(取决于i的某个值);
  12. //...
  13. }

情况A和情况B究竟哪个更高效?
A:经历一次构造析构,但有n次赋值
B:经历n次构造n次析构

这就要具体考虑赋值成本和构造+析构的成本了。


尽量少做转型动作

C风格的转型动作看起来如下:

  1. (T)expression
  2. T(expression)

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

  1. const_cast<T>();
  2. dynamic_cast<T>();
  3. reinterpret_cast<T>();
  4. static_cast<T>();

1.const_cast

将对象的常量性移除,即去掉const限定。
详情参考:C++新式转型之const_cast


2.dynamic_cast

主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个类型。
详情参考:C++新式转型之dynamic_cast


3.reinterpret_cast

意图执行低级转型实际动作可能取决于编译器。

  1. reinterpret_cast<new_type>(expression);

这是一个完全的编译器行为,它将expression的二进制序列解释成new_type。正因为他是从二进制的角度做转型,所以他可以“随便搞”,但也比较危险。
详情参考C++新式转型之reinterpret_cast


4.static_cast

用来强迫隐式转换(implict conversions),例如将non-const对象转为const对象,将int转为double。他也可以用来执行上述多种转换的反向转换,例如将void*转换为typed指针,将pointer-to-base转为pointer-to-derived(downcast)。但他无法将const转为non-const(const_cast)
详情参考:C++新式转型之static_cast

本条款中提到尽量少做转型操作举了几个例子:
1.使用static_cast作用于this往往不是作用于真正调用成员函数的对象,而是作用于该对象base域的一个副本。
2.使用dynamic_cast效率较低,之所以要用dynamic_cast因为我们手上有一个base指针,却想调用Derived的操作函数。作者提供了两种思路:一是使用容器并存储指向derived class对象的指针,二是使用虚函数。


避免返回handles指向对象内部成分

书中完整例子:

  1. #include <iostream>
  2. #include <memory>
  3. using namespace std;
  4. class Point{
  5. public:
  6. Point(int x, int y) :x_(x), y_(y){}
  7. Point() :x_(0), y_(0){}
  8. void setX(int x){ x_ = x; }
  9. void setY(int y){ y_ = y; }
  10. int & getX(){ return x_; }
  11. int & getY(){ return y_; }
  12. private:
  13. int x_;
  14. int y_;
  15. };
  16. struct RectPoint{
  17. Point left_up;
  18. Point right_down;
  19. };
  20. class Rect{
  21. public:
  22. Rect(Point p1, Point p2) :pdata(new RectPoint())
  23. {
  24. pdata->left_up.setX(p1.getX());
  25. pdata->left_up.setY(p1.getY());
  26. pdata->right_down.setX(p2.getX());
  27. pdata->right_down.setY(p2.getY());
  28. }
  29. Point &upperLeft()const { return pdata->left_up; }
  30. Point&lowerRight()const{ return pdata->right_down; }
  31. private:
  32. shared_ptr<RectPoint>pdata;
  33. };
  34. int main()
  35. {
  36. Point p1(0, 0);
  37. Point p2(100, 100);
  38. const Rect rec(p1, p2);
  39. rec.upperLeft().setX(10);
  40. getchar();
  41. return 0;
  42. }

当返回一个对象引用的时候,尽管成员函数const限定符限定了不能通过this指针修改成员函数,但返回的引用把数据成员暴露出来,也就可以直接调用setX接口,解决的办法是返回一个const reference

避免返回handles指向对象内部,他可以减少空悬指针发生的概率,也可以让const看起来行为就是const。


透彻了解inlining的里里外外

inline看起来像函数,动作像函数,比宏好得多,又可以不需要蒙受调用函数所招致的额外开销。

1.大量使用inline将增加目标码(object code)的大小。

2.inline只是对编译器的申请,并不是强制命令,这种命令可以隐式也可以是显示,其中隐式是在class内部定义函数,显示则是加上关键字inline

  1. class A
  2. {
  3. public:
  4. void hello(){
  5. cout<<"hello"<<endl;}//隐式
  6. };
  7. //显示,you
  8. template<typename T>
  9. inline const T&std::max(const T&a,const T&b)
  10. {
  11. return a<b?b:a;}

3.大部分编译器会拒绝太过复杂的函数inline,且所有的virtual函数的调用也不能inline,这很容易理解,virtual表示运行期在决定调用哪个函数,而inline是在编译器确定的。

4.构造函数可以inline,但编译器很可能不进行inline,即使你写了个空的构造函数,这个函数被处理这之后也未必是空的,编译器会在此构造函数中填写有一些内容,所有的基类的初始化,成员变量也要在此初始化如string。你写了空的构造函数,但也许这个构造函数会很大。析构同理。

5.大部分调试器对inline束手无策,因为无法在一个不存在的函数里面设置断点。

6.大多说inline限制在小型的被频繁调用的函数身上。

7.inline解决了宏定义的一些缺点:

用它替代宏定义,消除宏定义的缺点。宏定义使用预处理器实现,做一些简单的字符替换因此不能进行参数有效性的检测。另外它的返回值不能被强制转换为可转换的合适类型,且C++中引入了类及类的访问控制,在涉及到类的保护成员和私有成员就不能用宏定义来操作。

8.内联函数和宏定义的区别:

(1)内联函数在编译时展开,宏在预编译时展开;
(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
(3)内联函数有类型检测、语法判断等功能,而宏没有;
(4)inline函数是函数,宏不是;
(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;


将文件间的编译依存关系降到最低

接口和实现分离的方式,提供了两个办法:
1.Handle class
2.Interface class
前者就是Pimpl编程技法
后者就是利用纯虚函数来作为抽象接口的方式。


发表评论

表情:
评论列表 (有 0 条评论,212人围观)

还没有评论,来说两句吧...

相关阅读

    相关 effect C++笔记

    C+ +允许隐式类型转换,这是类型不安全的,为了防止隐式类型转换,可以使用explicit关键字阻止编译器进行隐式类型转换。 复制构造函数用于用一个对象初始化另一个对象,同时

    相关 Effective C++笔记(1)

    (一)前言 Effective C++是Scott Meyers编著,由侯捷老师翻译,也是STL源码剖析的作者。假期刚考完驾照,在家不能荒废学习,开始看看这本C++经典,