????????最近在學習C++的拷貝構造函數時發現一個問題:在函數中返回局部的類對象時,并沒有調用拷貝構造函數。針對這個問題,查閱了一些資料,這里記錄整理一下。
調用拷貝構造函數的三種情況:
① 用一個類去初始化另一個對象時(初始化的為新對象)
②一個對象作為參數,以值傳遞的方式傳入函數內
③ 返回值作為類對象,函數執行完成返回調用時。
下面寫了一個示例代碼:
#include <iostream>
#include <string>
using namespace std;
class Demo {
public:Demo(string name, int data) : m_name(name), m_data(data) {cout << "默認構造函數" << endl;}Demo(const Demo& other) {cout << "拷貝構造函數" << endl;m_name = other.m_name;m_data = other.m_data;}Demo& operator=(const Demo& other) {cout << "拷貝賦值運算符重載" << endl;m_name = other.m_name;m_data = other.m_data;return *this; //return *this 是為了可以連續賦值}Demo(const Demo&& other) {cout << "移動構造函數" << endl;m_name = other.m_name;m_data = other.m_data;}Demo& operator=(const Demo&& other) {cout << "移動賦值運算符重載" << endl;m_name = other.m_name;m_data = other.m_data;return *this;}
private:string m_name;int m_data;
};void test01()
{//默認構造Demo a("zhangsam", 10);Demo b("lisi", 20);//拷貝構造:使用一個類去初始化另一個對象時Demo c = a;//拷貝賦值運算符重載:使用一個類對另一個對象賦值c = b = a;//移動構造。使用右值對象對初始化一個對象時Demo e = move(a);//移動賦值運算符重載:使用一個右值對象對另一個對象賦值e = move(b);
}//當類對象做形參是,調用拷貝構造函數
Demo test02(Demo d1)
{Demo f("wangwu", 30);//返回一個類對象時,這里調用了移動構造函數//這里編譯器默認優化,需要增加-fno-elide-constructor編譯選項,但是調用的確實移動構造函數//原因是,在新的標磚中,當編譯器識別到返回的是一個局部的對象,將自動使用move轉化。//前提是類中自定義了移動構造函數,否則將調用拷貝構造函數return f;
}
int main()
{test01();cout << "-----------" << endl;Demo a1("test", 40);test02(a1);cout << "-----------" << endl;return 0;
}
最開始正常編譯 g++ test.cpp??
執行結果:
? ? ? ? 可以看到,test02函數最后返回一個f對象,但是并沒有調用拷貝構造函數。
①?Demo a1("test", 40);? //默認構造函數
② a1形參傳參 //拷貝構造函數
③ 函數內??Demo f("wangwu", 30); //默認構造函數
④ return f //???未打印任何東西
? ? ? ? 查閱資料后,說是需要增加一個編譯選項 -fno-elide-constructors, 果然增加后,出現了相應的打印。
但是,,為什么是調用的移動構造函數。。。
?再次查閱資料到:當從同類型的右值(亡值(將亡值))或純右值)(C++17前)亡值初始化(直接初始化或者復制初始化)對象時會調用移動構造函數,情況包括:
1、初始化 T a = std::move(b) 或 T a(std::move(b))
2、函數實參傳遞? f(std::move(a)) 其中a的類型是T 且f 是Ret f (T t);
3、函數返回:在像T f() 這樣的函數中的retuen a;,其中a的類型是T, 且T中自定義了移動構造函數。
? ? ? ? 所以,,,函數中的局部類對象其實是一個將亡值??
? ? ? ? 然后又百度了下將亡值的概念和定義:
? ? ? ? 就傳統的理解而言,函數foo的返回值在內部創建然后被賦值給v(外部接收返回值的對象),然后v獲得這個對象時,會將整個temp拷貝一份,然后把temp銷毀。如果這個temp非常大,這將造成大量額外的開銷(這也是c++一直被詬病的問題)。在新的特性里面,會自動檢測這個值是不死局部的,是的話,就直接move()了。用不同的編譯器,不同的開關(debug,relese)結果可能都不一樣。
? ? ? ? 例如,一個函數v = foo(),接收返回值的v是一個左值,foo()返回的值也就是一個右值(也是純右值)。但是v可以被別的變量捕獲到,而foo()產生的那個返回值作為一個臨時變量,一旦被v賦值后,將立即被銷毀,無法獲取,也不能修改。