在看《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: 3g++ 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,生成的临时变量并返回。