• 条款21: 必须返回对象时,不要强行返回对象的reference


    总结:

        绝不要返回一个local栈对象的指针或引用;绝不要返回一个被分配的堆对象的引用;绝不要返回一个静态局部对象(为了它,有可能同时需要多个这样的对象的指针或引用)。

        条款4中给出了“在单线程环境中合理返回局部静态对象的引用”。

    注意:利用指针返回一个被分配的堆对象是可以的。本条款讨论的是必须返回一个对象,所以返回一个对象的指针不包含在本条款内。那可能说,能返回对象的指针,为什么还需要返回一个对象呢?因为有的时候真的是需要返回一个对象,不能返回对象的指针。

     提出问题

     因为对象之间值传递是有效能的损耗的,所以引用传递的方式似乎更好,但是要避免一个错误:传递不存在的对象的引用。考虑一个用以表现有理数的类,包含一个函数计算两个有理数的乘积:

    class Rational {
    public:
       Rational(int numerator = 0, int denominator = 1);
        ...
    private:
       int n, d; // 分子与分母
       friend const Rational operator*(const Rational& lhs, const Rational& rhs);
    };
    

      这个版本operator* 以传值方式返回它的结果,需要付出对象的构造和析构成本。如果你能用返回一个引用来代替,就不需付出代价。但是,请记住一个引用仅仅是一个别名,一个实际存在的对象的名字。无论何时只要你看到一个引用的声明,应该立刻问自己它是什么东西的别名,因为它必定是某物的别名。以上述operator*为例,如果函数返回一个引用,它必然返回某个既有的而且包含两个对象相乘产物的Rational对象引用。

    当然没有什么理由期望这样一个对象在调用operator*之前就存在。也就是说,如果你有

    Rational a(1, 2);          // a = 1/2
    Rational b(3, 5);          // b = 3/5
    Rational c = a * b;        // c should be 3/10
    

       期望原本就存在一个值为3/10的有理数对象并不合理。如果operator*返回一个reference指向如此数值,它必须自己创建那个Rational对象


    函数创建新对象仅有三种方法:在栈或在堆上或者在static的静态区域。

    在栈空间上创建对象

    如果定义一个local变量,就是在栈空间创建对象

    const Rational& operator*(constRational& lhs, const Rational& rhs)
    {
       Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
       return result;
    }  //糟糕的代码!
    

       本来目的是避免调用构造函数,而result却必须像任何对象一样由构造函数构造而来。这个函数返回一个指向result的引用,但是result是一个局部对象,在函数退出时被销毁了。因此这个operator*的版本不会返回指向一个Rational的引用,它返回指向一个“从前的”Rational,一个旧时的Rational,一个曾经被当做Rational但如今已经成空、发臭、败坏的残骸,因为它已经被销毁。任何调用者甚至只是对此函数的返回值做任何一点点运用,就立刻进入了未定义行为的领地。这是事实,任何返回一个指向局部变量引用(或指针)的函数都是错误的

    在堆空间上创建对象

     考虑一下在堆上构造一个对象并返回指向它的引用的可能性。基于堆的对象通过使用new创建:

    const Rational& operator*(const Rational& lhs, const Rational& rhs)
    {
        Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
        return *result;
    }  //更糟的写法!
    

      谁该对用new创建出来的对象实施delete?

    Rational w, x, y, z;
    w = x * y * z; // 与operator*(operator*(x, y), z)相同
    

      这里,在同一个语句中有两个operator*的调用,因此new被使用了两次,这两次都需要使用 delete来销毁。但是operator*的客户没有合理的办法进行那些调用,因为他们没有合理的办法取得隐藏在通过调用operator*返回的引用后面的指针。这绝对导致资源泄漏。

    在静态数据区创建static对象

     无论是在栈还是在堆上的方法,为了从operator*返回的每一个 result,我们都不得不容忍一次构造函数的调用,而我们最初的目标是避免这样的构造函数调用。我们可以继续考虑基于 operator*返回一个指向staticRational对象引用的实现,而这个static Rational对象定义在函数内部:

    const Rational& operator*(constRational& lhs, const Rational& rhs)
    {
        static Rational result; // static对象,此函数返回其reference
        result= ... ;           // 将lhs乘以rhs,并将结果置于result内
        return result;
    }                          //又一堆烂代码!
    
    bool operator==(const Rational& lhs,const Rational& rhs);
    // 一个针对Rational所写的operator==
    Rational a, b, c, d;
    ...
    
    if ((a * b) == (c * d)) 
        {当乘积相等时,做适当的相应动作;}
    else 
        {当乘积不等时,做适当的相应动作}
    

      除了和所有使用static对象的设计一样可能引起的线程安全(thread-safety)的混乱,上面不管 a,b,c,d 的值是什么,表达式 ((a*b) == (c*d)) 总是等于 true!如果代码重写为功能完全等价的另一种形式,很容易了解出了什么意外:

    if (operator==(operator*(a, b), operator*(c, d)))
    

      在operator==被调用前,已有两个起作用的operator*调用,每一个都返回指向 operator*内部的staticRational对象的引用。两次operator*调用的确各自改变了staticRational对象值,但由于它们返回的都是reference,因此调用端看到的永远是static Rational对象的“现值”。因此operator==被要求拿operator*的static Rational对象值和operator*的static Rational对象值比较,如果不相等才奇怪。

    注意:本条款说的是不能利用static对象作为一个函数的返回值,这样会返回同一个静态对象。更不能为了不返回同一个静态对象,而开辟一个静态对象的数组,因为这样的代价更大。但是如果这种返回同一个静态对象的设计恰恰是我需要的单例模式,那就可以了,比如说条款4

    就让它返回一个新对象

     一个必须返回新对象的函数的正确方法:让那个函数返回一个新对象。对于Rational的 operator*,这就意味着下面这些代码或在本质上与其等价的代码:

    inline const Rational operator*(const Rational& lhs, const Rational& rhs)
    {return Rational(lhs.n * rhs.n, lhs.d * rhs.d);}
    

      当然,你可能付出了构造和析构operator*的返回值的成本,但是从长远看,这只是为正确行为付出的很小代价。但万一代价很恐怖,你可以允许编译器施行最优化,用以改善出码的效率却不改变其可观察的行为。因此某些情况下operator*返回值的构造和析构可被安全的消除。如果编译器运用这一事实(它们也往往如此),程序将保持应有行为,而执行起来又比预期的更快。

    总结:如果需要在返回一个引用和返回一个对象之间做决定,你的工作就是让那个选择能提供正确的行为,不能为了效能产生错误的行为。并不是说什么时候都不能返回对象的引用,下面的赋值拷贝构造函数返回对象的引用就是正确的。

    CMyString& operator =(const CMyString& str);
    
  • 相关阅读:
    Mysql登录错误:ERROR 1045 (28000): Plugin caching_sha2_password could not be loaded
    Docker配置LNMP环境
    Docker安装mysqli扩展和gd扩展
    Docker常用命令
    Ubuntu常用命令
    单例模式的优缺点和使用场景
    ABP 多租户数据共享
    ABP Core 后台Angular+Ng-Zorro 图片上传
    ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions.
    AbpCore 执行迁移文件生成数据库报错 Could not find root folder of the web project!
  • 原文地址:https://www.cnblogs.com/stemon/p/4578544.html
Copyright © 2020-2023  润新知