effective-c++系列:引用浅析

概念

  • 引用不是新定义一个变量,而是给一个已经存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

用法

  • 引用变量
1
2
int data = 0;
int& ref = data; //可读可写
  • 常引用变量

    1
    2
    int data = 0;
    int const& ref = data;//只读
  • 常引用常量

    1
    2
    const int data = 0;
    const int& ref = data;
  • 指针引用

    1
    2
    3
    4
    5
    6
    7
    8
    int data = 0;
    int *p = &data;
    int* &ref = p;
    cout << data << endl; //0
    cout << *p << endl; //0
    cout << *ref << endl; //0
    cout << p << endl;//0x61fe14
    cout << ref << endl;//0x61fe14
  • 引用变量的类型必须与它的实体类型一致(因为取别名要符合引用实体的身份,如果类型不一致则会报错)

  • 引用变量使用必须要进行初始化(不然没有实体都不知道给谁取别名)

  • 一个变量可以有多个引用(就相当于一个变量有好几个别名,这是可以的)

以pass-by-reference-to-const替换pass-by-value

  • 通常情况下c++使用值传递,传递对象都是由拷贝构造函数产生,这样做会产生非常昂贵的成本。每对一个对象进行值传递,就会进行一次拷贝构造函数和析构函数。
  • 考虑上述情况,可以使用引用传递来避免不必要的运行和内存开销。同时,如果希望在函数中不对对象进行任何改变,可以使用const关键字避免。
  • 在c++底层,引用是通过指针实现的,因此对于内置类型如int,double,float等使用值传递会有更高的效率。

必须返回对象时,不要使用引用

  • 考虑如下的例子,考虑在函数内生成一个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class A{
    int val;
    public:
    inline A(){};
    inline A(int x):val(x){};
    inline int getVal()const{
    return val;
    }
    };
    const A& operator*(const A& lhs, const A& rhs){
    A* res = new A(lhs.getVal() * rhs.getVal());
    return *res;
    }

    operator*函数内生成了一个对象,并在heap上分配了内存,那么这块内存永远不会再外部释放,因为离开函数作用域这个指针将自动销毁。
    那么使用下面这个函数生成临时对象也一样不可取,因为离开函数作用域,这个对象将被销毁。

    1
    2
    3
    4
    const A& operator*(const A& lhs, const A& rhs){
    A res(lhs.getVal() * rhs.getVal());
    return res;
    }

    能不能像下面这样声明一个静态对象来保存结果呢?

    1
    2
    3
    4
    5
    const A& operator*(const A& lhs, const A& rhs){
    static A res;
    res = A(lhs.getVal() * rhs.getVal());
    return res;
    }

    表面上看没有太大问题,但是在使用中我们常常会像一下这样来使用:

    1
    2
    3
    4
    5
    bool operator==(const A& lhs, const A& rhs);
    A r, s, t, x;
    if((r * s) == (t * x)){
    //do something
    }

    如果保存的是静态对象,那么这个表达式永远为真。表达式展开的结果是

    1
    if(operator==(operator*(r, s), operator*(t, x))){}

    在比较时永远比较的是静态对象值,不管执行顺序是什么,对象自身永远相等。

  • 正确的做法

    • 让函数返回一个新对象
    1
    2
    3
    4
    inline const A operator*(const A& lhs, const A& rhs){
    A res(lhs.getVal() * rhs.getVal());
    return res;
    }