C++方法返回值为引用时会发生什么

问题来源

在学习QT的过程中,看到有些内置的QT库中存在一些返回引用的方法,例如:

QByteArray &QByteArray::append(const char *str, int len);
QByteArray &QByteArray::append(char ch);

等等,所以有点好奇,返回值与返回引用之间有什么区别呢?遂有了这篇文章。

在写验证程序的时候又对c++的构造函数、拷贝构造函数、赋值构造函数有了新的理解,文章末会放出完整的测试代码。

第一步,我们需要一个类,给他起个名字,就叫A吧。
class A {
public:
  A(int i = 0) : mi(i) { qDebug() << this << "A 对象通过构造函数创建" << mi; }
  A(const A &y) {
    qDebug() << this << "A 对象通过拷贝构造函数创建" << mi <<"被拷贝的对象是:"<<&y;
  }
  ~A() { qDebug() << this << "~A()" << mi; }
  void run() { qDebug() << this << "run()" << mi; }
public:
  int mi;
};

这个A类,我们重写了它的构造函数、拷贝构造函数、析构函数以便使我们可以观测到程序的执行步骤。

第二步,我们还需要几个返回A的方法:
static A newA(A &a) {
  //其作为
  A aa(1);
  return aa;
};
static A newA2(A &a) {
  A aa(2);
  return a;
};
static A &newA3(A &a) {
  A aa(3);
  return aa;
}
第三步,开始测试
int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);
  {
    A aaa(33);
     A a4 = newA(aaa);
    // A a4 = newA2(aaa);
    // A a4 = newA3(aaa);
    a4.run();
    qDebug() << "a4.run();";
  }
  return a.exec();
}

上面程序的执行结果为:

0x65fe9c A 对象通过构造函数创建 33
0x65fe98 A 对象通过构造函数创建 1
0x65fe98 run() 1
a4.run();
0x65fe98 ~A() 1
0x65fe9c ~A() 33

aaa通过引用传递的方式传入newA方法,这个过程不会发生拷贝构造,然后newA方法内,A aa(1);语句通过构造函数创建了一个对象aa,然后它作为返回值返回,并且aa对象的生存周期延长至程序结束。

  • 接着修改main函数,改为调用newA2方法。运行结果如下:
0x65fe9c A 对象通过构造函数创建 33
0x65fe6c A 对象通过构造函数创建 2
0x65fe98 A 对象通过拷贝构造函数创建 6684520 被拷贝的对象是: 0x65fe9c
0x65fe6c ~A() 2
0x65fe98 run() 6684520
a4.run();
0x65fe98 ~A() 6684520
0x65fe9c ~A() 33

newA2方法与newA方法唯一的不同便是返回的值不同,newA2方法返回的不是在newA2方法内创建的局部变量,而是传入的实参。
因此程序运行的结果在这发生了不同,在newA2方法的最后,调用拷贝构造函数构造了一个新的对象,这个新的对象作为返回值返回。
我们可以看到执行run()方法时,mi值变为了6684520而不是33,这是因为我们重载了拷贝构造函数,并且啥也不干,如果我们不重载拷贝构造函数的话,mi的值会是33。不信你试试/滑稽。我不知道这是为啥,改天再研究下。

  • 继续修改main函数,改为调用newA3方法。运行结果如下:
0x65fe9c A 对象通过构造函数创建 33
0x65fe6c A 对象通过构造函数创建 3
0x65fe6c ~A() 3
0x65fe98 A 对象通过拷贝构造函数创建 6684520 被拷贝的对象是: 0x0
0x65fe98 run() 6684520
a4.run();
0x65fe98 ~A() 6684520
0x65fe9c ~A() 33

这段输出乍看上去跟上面那个很相似,但是。。。。。
输出的不同点在于输出的第三、四行。调用newA3方法,是先析构局部变量aa,然后再拷贝构造实参aa。这个顺序就有问题了啊。已经被析构的局部变量,如何才能作为拷贝构造方法的参数呢? 虽然程序正常运行了,但是存在访问非法内存的风险。


下面放出完整的测试代码

#include <QCoreApplication>
#include <QDataStream>
#include <QtDebug>
class A {
public:
  A(int i = 0) : mi(i) { qDebug() << this << "A 对象通过构造函数创建" << mi; }
  //拷贝构造,一般在传参时使用,或者是返回非引用(仅限返回值为引用传参),或者是
  A(const A &y) {
    qDebug() << this << "A 对象通过拷贝构造函数创建" << mi << "被拷贝的对象是:"
             << &y << y.mi;
  }
  ~A() { qDebug() << this << "~A()" << mi; }
  void run() { qDebug() << this << "run()" << mi; }

  //赋值构造函数
  A &operator=(const A &other) {
    qDebug() << "=赋值构造函数" << this << mi << &other << other.mi;
    this->mi = other.mi;
    return *this;
  }

public:
  int mi;
};

//若是实参以值传递方式传入,那么会调用拷贝构造函数构造a。
static A newA(A &a) {
  //其作为
  A aa(1);
  return aa;
};
//以值传递方式返回,那么会调用拷贝构造函数构造一个返回值。
//若是实参以值传递方式传入,那么还会调用拷贝构造函数构造a,a的析构发生在上行拷贝构造之后。
static A newA2(A &a) {
  //其作为
  A aa(2);
  return a;
};

//返回引用时,会调用拷贝构造函数构造出一个返回值,不同于newA2,拷贝构造发生在
// A aa(3)对象析构之后。!!!!!!该问题导致,被拷贝的对象已经被析构,导致程序会出现漏洞。
//实参为值传递时,仍会调用拷贝构造函数,但是该对象在拷贝构造之后才会析构
static A &newA3(A &a) {
  A aa(3);
  return aa;
}
//实参为值传递时,输出变得很奇怪
static A &newA4(A &a) {
  A aa(4);
  return a;
}

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);
  {
    A aaa(33);
    //调用拷贝构造函数
    // A aa = aaa;
    //
    // A aa = newA(aaa);
    //调用拷贝构造函数
    // A aa = newA2(aaa);
    //调用拷贝构造函数
    // A aa = newA3(aaa);
    //调用拷贝构造函数
    A aa = newA4(aaa);

    // aa = newA(aaa);

    // aa = newA2(aaa);
    // aa = newA3(aaa);//调用该方法,程序会gg
    // aa = newA4(aaa);

    // aa = newA2();
    aa.run();
    qDebug() << "aa.run();";
  }
  return a.exec();
}


刚开始学qt、c++,所以也没考虑编译器会不会优化代码啊之类的,如果有错误的地方,恳请大家评论指出。
第一次自己写,自己分析,自己研究,这不到百行的代码折腾了一下午,还没弄明白。c++果然好难。c#真的好好用,这些都不用自己操心。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值