在看《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_(), 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_ = ;
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();
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 ;
}
運作結果如下:
- 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();
a = fun();
}
return ;
}
- 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
傳回;如果 return 的變量為左值,則函數調用傳回時将其copy,生成的臨時變量并傳回。A