c++类临时对象、复制构造函数、析构函数 VS2017 与gcc 编译器对比

探讨C++中函数调用时临时对象的创建机制,对比VS2017与g++的不同行为,深入分析参数传递及返回过程中的对象复制细节。

在看《C和C++程序员面试秘籍》的时候,看到第6章28与29条,有些疑惑:

  • 作者说函数调用时,参数传递进来,以及返回时,都会构造临时对象。
    也就是说,会有两个临时对象被创建,一个是在参数传递进来的时候,一个是在返回的时候。

先说实验结论:

  • 书中的代码在 VS2017中实测没有问题,输出结果完全一致;
  • 但在 GCC 下,代码有问题,输出结果有一些不一致(即:g++ 编译器在函数返回时,不一定创建临时变量;且不接受临时构建的类对象作为复制构造函数的实参)。

表述的可能不严谨,实验如下

先写一个用来测试的类,其所有的构造函数,赋值operator等,只做基本的数据赋值,和输出信息:

myclass.h 内容:

#ifndef _MY_CLASS_
#define _MY_CLASS_

#include <iostream>
#include <string>
namespace myclass {

class A {
public:
  // 默认构造函数
  A() : data_(0), name_("default") {
    count_++;
    std::cout << "default constructor, data: " << data_ << ", name: " << name_
              << ", count: " << count_ << std::endl;
  }
  // 含参构造函数
  A(int data, std::string name = "default") : data_(data), name_(name) {
    count_++;
    std::cout << "param constructor, data: " << data_ << ", name: " << name_
              << ", count: " << count_ << std::endl;
  }
  // 复制构造函数
  A(A &other) {
    count_++;
    data_ = other.data_;
    name_ = other.name_ + "_copy";
    std::cout << "copy constructor, data: " << data_ << ", name: " << name_
              << ", count: " << count_ << std::endl;
  }

  // 下面是右值复制构造函数,没有这个函数,面试题28在 g++ 编译器下无法编译通过,会输出如下错误信息:
  // error: invalid initialization of non-const reference of type 'myclass::A&' from an rvalue of type 'myclass::A'
  //   A t1 = Play(5);
  // 意思是临时类对象(右值)无法用来初始化类的左值引用(即:A&)
  // 但在VS2017下,没有这个右值复制构造函数,是可以编译通过的,会调用上面定义的复制构造函数

  A(A &&other) {
    count_++;
    data_ = other.data_;
    name_ = other.name_ + "_copy";
    std::cout << "copy [rvalue] constructor, data: " << data_
              << ", name: " << name_ << ", count: " << count_ << std::endl;
  }

  A &operator=(const A &a);

  // name_ setter and getter
  const std::string &name() { return name_; }
  void set_name(const std::string &name) { name_ = name; }
  // destructor
  virtual ~A() {
    std::cout << "destructor, data: " << data_ << ", name: " << name_
              << ", count: " << count_ << std::endl;
  }

  static int count_;

private:
  int data_;
  std::string name_;
};

int A::count_ = 0;

A &A::operator=(const A &a) {
  std::cout << "operator = function, before assignment data: " << data_
            << ", name:" << name_ << std::endl;
  if (this == &a) {
    std::cout << "                   afeter assignment[same obj] data: "
              << data_ << ", name:" << name_ << std::endl;
    return *this;
  }

  data_ = a.data_;
  name_ = a.name_;
  std::cout << "                     afeter assignment data: " << data_
            << ", name:" << name_ << std::endl;
  return *this;
}

} // namespace myclass

#endif

A 里面的数据只有一个 data_ 和一个 name_ ,和一个静态变量 count_ , 它们主要用来输出的时候查看是哪一个类被创建和析构。

面试题28:

主函数(main_28.cpp):

#include <iostream>
#include <string>
#include "myclass.h"

using namespace std;
using namespace myclass;

A Play(A a) { return a; }

int main(int argc, char *argv[]) {
  A t1 = Play(5);
  cout << "t1.name = " << t1.name() << endl;
  t1.set_name("t1");

  A t2 = Play(t1);
  cout << "t2.name = " << t2.name() << endl;
  t2.set_name("t2");

  return 0;
}

运行结果如下:

  • VS2017 输出

param constructor, data: 5, name: default, count: 1
copy [rvalue] constructor, data: 5, name: default_copy, count: 2
destructor, data: 5, name: default, count: 2
t1.name = default_copy
copy constructor, data: 5, name: t1_copy, count: 3
copy [rvalue] constructor, data: 5, name: t1_copy_copy, count: 4
destructor, data: 5, name: t1_copy, count: 4
t2.name = t1_copy_copy
destructor, data: 5, name: t2, count: 4
destructor, data: 5, name: t1, count: 4

  • g++ 6.2.0 输出:

    param constructor, data: 5, name: default, count: 1
    copy [rvalue] constructor, data: 5, name: default_copy, count: 2
    destructor, data: 5, name: default, count: 2
    t1.name = default_copy
    copy constructor, data: 5, name: t1_copy, count: 3
    copy [rvalue] constructor, data: 5, name: t1_copy_copy, count: 4
    destructor, data: 5, name: t1_copy, count: 4
    t2.name = t1_copy_copy
    destructor, data: 5, name: t2, count: 4
    destructor, data: 5, name: t1, count: 4


可以看到,结果完全一样。
其中,第2行输出说明:临时变量在传入Play() 函数时,确实利用copy构造函数构造了临时类对象(name: default_copy),但是在函数返回时,并没有构造另一个临时变量 default_copy_copy(也就是说这次函数调用,只有在参数传入时,有临时变量生成,函数返回并没有生成另一个临时变量)。用t1赋值给t2时,也是如此。

面试题29:

我们再看面试题29,情况就不一样了,在函数返回时,VS2017确实生成了一个临时类对象,而g++依然没有生成。

主函数( main_29.cpp ):

#include <iostream>
#include "myclass.h"

using namespace std;
using namespace myclass;

A fun() {
  A a;
  return a;
}

int main(int argc, char *argv[]) {
  {
    A a(5);
    a = fun();
  }
  return 0;
}
  • VS2017 输出

    param constructor, data: 5, name: default, count: 1
    default constructor, data: 0, name: default, count: 2
    copy [rvalue] constructor, data: 0, name: default_copy, count: 3
    destructor, data: 0, name: default, count: 3
    operator = function, before assignment data: 5, name:default
    afeter assignment data: 0, name:default_copy
    destructor, data: 0, name: default_copy, count: 3
    destructor, data: 0, name: default_copy, count: 3

  • g++ 6.2.0 输出:

    param constructor, data: 5, name: default, count: 1
    default constructor, data: 0, name: default, count: 2
    operator = function, before assignment data: 5, name:default
    afeter assignment data: 0, name:default
    destructor, data: 0, name: default, count: 2
    destructor, data: 0, name: default, count: 2

从VS2017 的第3-4行输出可以看到,VS2017在函数 fun() 返回时构造了临时变量 default_copy, 而 g++ 编译器并没有,还是挺有意思的。

总结一下:
在这个实验里,g++编译器 在函数调用结束后,并没有生成类的临时拷贝用来返回;而VS2017有时候生成,有时候不生成。

  • 猜想:当 return 的变量本身就是临时变量(即右值)A 时,不再生成临时变量 B,而是直接将A返回;如果 return 的变量为左值,则函数调用返回时将其copy,生成的临时变量并返回。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值