14-Qt中为什么函数可以直接返回QString对象而不是QString*(指针),复制构造函数的作用

Qt中为什么函数可以直接返回QImage/QString对象,而不是QImage* QString*(指针)

副标题:C++编程中使用List<Object*>还是List<Object>呢 ?

注:本文涉及到的主要知识点为:C++的拷贝构造函数的作用

一、在C++中返回参数的基础知识

在C++中,函数返回类型有,基本数据类型,结构体,对象,指针。下面举例几个正确使用C++中函数返回值的使用方法:

  1. 返回c++基本类型
    int max(int a, int b) {
    return a > b ? a : b;
    }
  2. 返回指针:返回的指针所指的对象必须是用new生成的。
    优点:返回指针,加快了参数传递速度
    缺点:new需要花费不少时间
    优点:在单实例模式需要使用这个方式
    Apple *GetAnApple() {
    Apple *apple*= new Apple();
    return apple;
    }
  3. 返回对象:直接返回对象,在return的时候会调用类的拷贝构造函数,给返回值赋值,然后函数内的局部变量就会被销毁。
    缺点:1.返回前还要调用类的拷贝构造函数新建并复制全部成员变量。计算压力增大。
    缺点:2.在类的成员变量中包含静态成员或指针成员的时候需要自己实现拷贝函数,默认的拷贝构造函数会导致错误;参考:https://blog.csdn.net/lwbeyond/article/details/6202256
    Banana GetABanana() {
    Banana banana;
    return banana;
    }
  4. 返回引用:这个是错误的不可用的,局部变量在函数执行完会被销毁。因此返回局部变量的引用是不可行的。高级点的编辑器在你这样写的时候就会提示错误。
    Banana& GetABanana() {
    Banana banana;
    return banana;
    }

二、在C++中可以直接返回String对象或QImage对象

  1. 为什么要直接返回对象
    由于“一.3 返回对象”可知直接返回对象是可行的。但是为什么要设计为允许返回一个对象呢,字符串很大时候,复制一个字符串很浪费内存。同理在Qt中返回QImage对象也非常浪费内存,为什么要这样设计呢?

  2. 原因梳理
    一般使用方法
    String GetAStr() {
    String a = “1”;
    return a;
    }
    String b = GetAStr();
    但是人家这样设计了肯定是有原因的。原因如下:
    返回对象肯定用到了QString类的拷贝构造函数,但是了避免把字符也再次复制一遍,应该是用了指针指向同一份“字符串”,因此在拷贝构造函数只要复制指针的值即可。因此直接返回String对象是可行的,返回对象并不会浪费内存。同理返回QImage也是这样的原理。

  3. 官方文档给出的解释
    在Qt的QImage文档中看到如下内容:
    “QImage objects can be passed around by value since the QImage class uses implicit data sharing.”
    翻译:QImage对象可以当成值直接传递,因为QImage的实现使用了“隐式数据分享”
    关于Qt的“隐式数据分享”核心介绍文字如下:
    Many C++ classes in Qt use implicit data sharing to maximize resource usage and minimize copying. Implicitly shared classes are both safe and efficient when passed as arguments, because only a pointer to the data is passed around, and the data is copied only if and when a function writes to it, i.e., copy-on-write.
    A shared class consists of a pointer to a shared data block that contains a reference count and the data.
    翻译:在Qt中很多C++类使用隐式数据共享来最大化利用现有资源避免了复制数据。隐式分享在作为参数传递的时候具有安全和效率,因为传递的内容只有一个指向数据的指针。只有当一个函数修改了被指向数据的内容,才进行真正的数据拷贝(写时复制)。

一个共享类由一个指向共享数据块的指针组成。这个共享数据块包含实际数据和这个数据被引用的次数。

详情:https://doc.qt.io/qt-5/implicit-sharing.html
Qt中使用了隐式数据分享的类有:QJsonArray,QList,QString,QImage,QPen,QHostAddress等,详情表在以上详情链接里面有。

三、在定义对象列表的时候使用List<Apple>还是List<Apple*>好呢?

添加数据时候的区别:
“//———————List<Apple>”
Apple a;//局部变量初始化消耗时间,消耗栈内存
List.appene(a);//添加到列表需要调用类的拷贝构造函数,需要消耗复制成员变量的时间,(或处理自定义拷贝构造函数的时间,如果使用自定义拷贝构造函数)
“//———————List<Apple*>”
Apple *a = new Apple();//采用new内存需要消耗时间,消耗堆内存
list.append(a);

从以上分析可知:

  1. 内存使用:new一个对象和初始化一个局部变量所消耗的内存与cpu计算量相差无几。
  2. 添加速度:
    2.1 添加一个List<Apple>项目需要的时间包括:新建局部变量,初始化变量,拷贝构造对象。
    2.2 添加一个List<Apple*>项目需要的时间包括:申请内存新建变量,初始化变量。(这个两个速度无法预估,没有做实际测试)
  3. 访问速度:C++访问对象也是用指针访问,访问指针对象也是指针访问,因此调用List的Item的时候速度也相差无几。
  4. 功能限制:使用List<Apple>的时候需要使用类的拷贝构造函数,因此就有了限制,自己不写拷贝构造函数,c++编译器会提供隐式拷贝构造函数。但是当类里面有静态成员变量或指针成员变量的时候,编译器提供的隐式拷贝构造函数就会造成程序错误。因此需要显式编写拷贝构造函数的代码。(这点比较重要)使用List<Apple*>的时候,没有拷贝构造函数的限制。

从以上分析可以得到以下结论:

  1. 使用List<Apple*>会使得代码简洁,容易编码,初学者也可以编写出正确稳定的代码(因为不用考虑是否需要编写自己的拷贝构造函数,以及具体编写)。
  2. 在使用特定的类的时候比如QString的时候,直接返回对象是更有优势的,避免了(new、delete)自己做内存管理。
  • 注1:在Java中使用了List<Apple>的格式但是其实相当于C++的List<Apple*>。(java表面没有指针的概念和语法,但是内在都是用了指针的思想和实现)
  • 注2:一般设计C++应用程序的时候,为了避免隐式调用拷贝构造函数而引起的问题。一般都显式指定一个私有的拷贝构造函数。(同理,指定一个显式私有赋值构造函数)。在qt中的方法为:private:Q_DISABLE_COPY(Class) ,这个宏定义的实现为:
    #define Q_DISABLE_COPY(Class)
    Class(const Class &) Q_DECL_EQ_DELETE;\ //拷贝构造函数
    Class &operator=(const Class &) Q_DECL_EQ_DELETE; //赋值构造函数
    原文作者:robert_cysy
    原文地址: https://blog.csdn.net/robert_cysy/article/details/102658529
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞