第13章 拷貝控制
從此章我們即将開始第三部分的學習,之前我們已經學過了兩個部分,C++基礎和C++标準庫,第三部分為類設計者的工具
也就是我們即将開始傳說中的對象對象程式設計之旅,面向對象程式設計(Object Oriented Programming)
本章進行學習類如何操控該類型的拷貝,指派,移動或者銷毀,有:拷貝構造函數、移動構造函數、拷貝指派運算符、移動指派運算符以及析構函數等重要知識
拷貝構造函數
定義:如果一個構造函數的第一個參數是自身類類型的引用,且任何額外參數都有預設值,則此構造函數是構造拷貝函數
簡單上手
//example1.cpp
class Person
{
public:
int age;
Person() = default;
Person(int age) : age(age) {}
Person(const Person &person)
{
//内容拷貝
this->age = person.age;
}
};
int main(int argc, char **argv)
{
Person person1(19);
Person person2 = person1;
cout << person2.age << endl; // 19
return 0;
}
合成拷貝構造函數
預設情況下,編譯器會定義一個拷貝構造函數,即使在我們提供拷貝構造函數的情況下也仍會自動生成,預設情況下會将每個非static成員拷貝到正在建立的對象中
//example2.cpp
class Person
{
public:
int age;
string name;
Person() = default;
Person(const Person &);
Person(const int age, const string name) : age(age), name(name)
{
}
};
//直接使用構造函數初始化清單
//此定義與預設合成拷貝函數相同
Person::Person(const Person &person) : age(person.age), name(person.name)
{
}
int main(int argc, char **argv)
{
Person me(19, "gaowanlu");
Person other = me;
// 19 gaowanlu
cout << other.age << " " << other.name << endl;
return 0;
}
嘗試測試一下編譯器預設提供的合成拷貝構造函數,可見存在預設合成拷貝構造函數
如果不想讓一個構造函數具有可以指派轉換的功能,則将其定義為explicit的
//example3.cpp
class Person
{
public:
string name;
int age;
Person(const int age, const string name) : name(name), age(age) {}
};
int main(int argc, char **argv)
{
Person me(19, "gaowanlu");
Person other = me;
// 19 gaowanlu
cout << other.age << " " << other.name << endl;
return 0;
}
重載指派運算符
重載
operator=
方法進行自定義指派運算符使用時要做的事情
//example4.cpp
class Person
{
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name) {}
Person &operator=(const Person &);
};
Person &Person::operator=(const Person &person)
{
cout << "operator =" << endl;
this->age = person.age;
this->name = person.name;
return *this;
}
int main(int argc, char **argv)
{
Person person1(19, "me");
Person person2;
person2 = person1; // operator =
cout << person2.age << " " << person2.name << endl; // 19 me
return 0;
}
合成拷貝指派運算符
與合成拷貝構造函數類似,如果沒有自定義拷貝指派運算符,編譯器會自動生成
//example5.cpp
class Person
{
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name) {}
};
int main(int argc, char **argv)
{
Person person1(19, "me");
Person person2;
person2 = person1; //使用預設合成拷貝指派運算符
cout << person2.age << " " << person2.name << endl; // 19 me
return 0;
}
析構函數
析構函數與構造函數不同,構造函數初始化對象的非static資料成員,還可能做一些在對象建立時需要做的事情。析構函數通常釋放對象的資源,并銷毀對象的非static資料成員
~TypeName();
析構函數沒有傳回值,沒有接收參數,是以其沒有重載形式
在構造函數中,初始化部分執行在函數體執行前,析構函數則是首先執行函數體,然後按照初始化順序的逆序銷毀。
構造函數被調用的時機
- 變量在離開其作用域時被銷毀
- 當一個對象被銷毀時,其成員被銷毀
- 容器(無論标準容器還是數組)被銷毀時,其元素被銷毀
- 動态記憶體配置設定,當對它的指針使用delete時被銷毀
- 對于臨時對象,當建立它的完整表達式結束時被銷毀
//example6.cpp
class Person
{
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name) {}
~Person()
{
cout << "~Person" << endl;
}
};
Person func(Person person)
{
return person;
}
int main(int argc, char **argv)
{
Person person(19, "me");
Person person1 = func(person);
//~Person被列印三次
//首先将person拷貝給func的形參,然後形參person作為傳回值指派給person1
//然後func傳回值person被銷毀
//随着main執行完畢,main内的兩個Person被銷毀
return 0;
}
合成析構函數
當為自定義析構函數時,編譯器會自動提供一個合成析構函數,對于某些類作用為阻止該類型的對象被銷毀,如果不是則函數體為空
//example7.cpp
class Person
{
public:
int age;
string name;
Person() = default;
Person(int age, string name) : age(age), name(name) {}
~Person() {} //等價于合成析構函數
};
int main(int argc, char **argv)
{
Person person(19, "gaowanlu");
cout << person.age << " " << person.name << endl; // 19 gaowanlu
return 0;
}
在合成析構函數體執行完畢之後,成員會被自動銷毀,對象中的string被銷毀時,将會調用string的析構函數,将name的記憶體釋放掉,
析構函數自身并不直接銷毀成員
,是在析構函數體之後隐含的析構階段中被銷毀的,整個銷毀過程,析構函數體是作為成員銷毀步驟之外的并一部分而進行的
如果對象的内部有普通指針記錄new動态記憶體,在對象析構過程預設隻進行指針變量指針本身的釋放,而不對申請的記憶體進行釋放,則就需要動态記憶體章節學習的在析構函數體内手動釋放他們,或者使用智能指針,随着智能指針的析構被執行,動态記憶體會被釋放
三/五法則
有三個基本操作可控制類的拷貝操作:拷貝構造函數、拷貝指派運算符、析構函數。在新标準下還可以通過定義一個移動構造函數、一個移動指派運算符
我們發現有時指派運算符與拷貝構造函數會執行相同的功能,通常情況下并不要求定義所有這些操作
使用合成拷貝函數和合成拷貝指派運算符時可能遇見的問題
//example8.cpp
class Person
{
public:
int age;
string *name;
Person(const string &name = string()) : name(new string(name)), age(0) {}
~Person()
{
delete name;
}
};
int main(int argc, char **argv)
{
{
Person person1("me");
Person person2 = person1; //使用合成拷貝構造函數
//此時的person1.name與person2.name指向相同的記憶體位址
*person1.name = "he";
cout << *person2.name << endl; // he
}
cout << "end" << endl; // end
return 0;
}
在合成拷貝構造函數和合成拷貝指派運算符,其中的拷貝操作都是簡單的指針位址指派,而不是重新開辟空間,再将原先的name指派到新的記憶體空間
使用=default
使用
=default
可以顯式要求編譯器生成合成拷貝構造函數和拷貝指派運算符
//example9.cpp
class Person
{
public:
Person() = default; //合成預設構造函數
Person(const Person &) = default; //合成拷貝構造函數
Person &operator=(const Person &); //合成拷貝指派運算
~Person() = default; //合成析構函數
};
//預設在類内使用=default的成員函數為内聯的
//如果不希望是内聯函數則應在類外部定義使用=default
Person &Person::operator=(const Person &person) = default;
int main(int argc, char **argv)
{
Person person1;
Person person2 = person1;
cout << "end" << endl; // endl
return 0;
}
=delete阻止拷貝
使用=delete
定義删除的函數
//example10.cpp
class Person
{
public:
Person() = default;
Person(const Person &) = delete; //禁止拷貝構造函數
Person &operator=(const Person &) = delete; //阻止拷貝指派
~Person() = default;
};
int main(int argc, char **argv)
{
Person person1;
// Person person2 = person1;//錯誤 不允許拷貝複制指派
return 0;
}
析構函數不能是删除的成員
,否則就不能銷毀此類型,沒有析構函數的類型可以使用動态配置設定方式建立,但是不能被銷毀
//example11.cpp
class Person
{
public:
int age;
string name;
Person(const int age, const string name) : age(age), name(name) {}
~Person() = delete;
};
int main(int argc, char **argv)
{
Person *person = new Person(19, "me");
// delete person;//錯誤 Person沒有析構函數
return 0;
}
編譯器将成員處理為删除的
對于某些情況,編譯器會将合成的成員定義為删除的函數
重點:如果一個類有資料成員不能預設構造、拷貝、複制、銷毀,則對應的成員函數将被定義為删除的
private拷貝控制
在新标準之前沒有,删除的成員,類是通過将其拷貝構造函數和拷貝指派運算符聲明為private的來阻止拷貝的
//example12.cpp
class Person
{
private:
Person(const Person &person);
Person &operator=(const Person &person);
public:
int age;
string name;
Person(const int age, const string name) : age(age), name(name) {}
~Person() = default;
Person() = default;
void test();
};
Person::Person(const Person &person)
{
}
Person &Person::operator=(const Person &person)
{
return *this;
}
void Person::test()
{
Person *person = new Person(19, "me");
Person person1 = *person; //函數成員或者友元函數可以使用
delete person;
}
int main(int argc, char **argv)
{
Person person1(19, "me");
// Person person2 = person1;
// error: 'Person::Person(const Person&)' is private within this context
person1.test();
return 0;
}
這種雖然類的外部不能使用拷貝構造和拷貝指派,但是類的友元和成員函數仍可使用二者,同時想要阻止友元函數或者成員函數的使用,則隻聲明private成員即可不進行定義
//example13.cpp
class Person
{
private:
Person(const Person &person); //隻聲明不定義
Person &operator=(const Person &person); //隻聲明不定義
public:
int age;
string name;
Person(const int age, const string name) : age(age), name(name) {}
~Person() = default;
Person() = default;
void test();
};
int main(int argc, char **argv)
{
Person person1(19, "me");
// Person person2 = person1;
// error: 'Person::Person(const Person&)' is private within this context
// 如果函數成員或友元函數使用拷貝構造或者指派 也會報錯
return 0;
}
總之優先使用=delete這種新的規範,delete是從編譯階段直接解決問題
行為像值的類
有些類拷貝是值操作,是一份相同得副本
//example14.cpp
class Person
{
public:
int *age;
string *name;
Person(const int &age, const string &name) : age(new int(age)), name(new string(name)) {}
Person() : age(new int(0)), name(new string("")) {}
Person &operator=(const Person &person);
~Person()
{
delete age, delete name;
}
};
Person &Person::operator=(const Person &person)
{
*age = *person.age;
*name = *person.name;
return *this;
}
int main(int argc, char **argv)
{
Person person1(19, "me");
Person person2(20, "she");
person1 = person2;
cout << *person1.age << " " << *person1.name << endl; // 20 she
cout << *person2.age << " " << *person2.name << endl; // 20 she
*person1.name = "gaowanlu";
cout << *person1.age << " " << *person1.name << endl; // 20 gaowanlu
cout << *person2.age << " " << *person2.name << endl; // 20 she
//可見之間此類對象像一種值類型
return 0;
}
行為像指針的類
有些類拷貝是指針指向的操作,也就是不同的類的成員會使用相同的記憶體
先來看一種簡單使用的情況
//example15.cpp
class Person
{
public:
int *age;
string *name;
Person() : age(new int(0)), name(new string) {}
Person(const int &age, const string &name) : age(new int(age)), name(new string(name)) {}
Person &operator=(const Person &person);
};
Person &Person::operator=(const Person &person)
{
if (age)
delete age;
if (name)
delete name;
age = person.age;
name = person.name;
return *this;
}
int main(int argc, char **argv)
{
Person person1(19, "me");
Person person2 = person1;
// person1 person2的内容的記憶體是相同的
*person2.age = 20;
*person2.name = "gaowanlu";
cout << *person1.age << " " << *person1.name << endl; // 20 gaowanlu
cout << *person2.age << " " << *person2.name << endl; // 20 gaowanlu
return 0;
}
實作引用計數
有意思的例子是我們也可以設計引用計數的機制,通過下面這個例子可以學到很多的程式設計思想
//example16.cpp
class Person
{
public:
string *name;
int *age;
Person(const int &age = int(0), const string &name = string("")) : use(new size_t(1)), age(new int(age)), name(new string(name)) {}
//拷貝構造時
Person(const Person &person)
{
name = person.name;
age = person.age;
use = person.use;
++*use; //引用數加一 不能寫 *use++哦 因為那是*(use++)
}
//指派拷貝時
Person &operator=(const Person &person);
//析構時
~Person();
size_t *use; //引用計數器
};
//拷貝指派
Person &Person::operator=(const Person &person)
{
//遞增右邊對象的引用系數
++*person.use;
//遞減本對象引用計數
--*use;
if (*use == 0)
{
delete age, delete name, delete use;
}
age = person.age;
name = person.name;
use = person.use;
return *this;
}
//析構
Person::~Person()
{
//将引用數減1
--*use;
//判斷引用數是否為0
if (*use == 0)
{
delete age, delete name, delete use;
}
}
int main(int argc, char **argv)
{
Person person1(19, "me");
cout << *person1.use << endl; // 1
{
Person person2(person1);
cout << *person1.use << endl; // 2
Person *ptr = new Person(person2);
cout << *ptr->use << endl; // 3
delete ptr;
cout << *person1.use << endl; // 2
}
cout << *person1.use << endl; // 1
//最後當person1銷毀時 析構函數内引用計數變為0 随之将記憶體釋放 達到記憶體管理的效果
return 0;
}
編寫swap函數
可以在類上定義一個自己的swap函數重載swap預設行為
//example17.cpp
class Person
{
//聲明為友元函數可通路類私有成員
friend void swap(Person &a, Person &b);
public:
int age;
string name;
Person(const int &age, const string &name) : age(age), name(name) {}
};
//定義函數 void swap(Person &a, Person &b);
inline void swap(Person &a, Person &b)
{
std::swap(a.age, b.age);
std::swap(a.name, b.name);
}
int main(int argc, char **argv)
{
Person person1(19, "me");
Person person2(19, "she");
swap(person1, person2);
cout << person1.age << " " << person1.name << endl; // 19 she
cout << person2.age << " " << person2.name << endl; // 19 me
return 0;
}
拷貝指派運算中使用swap
類的swap通常用來定義它們的指派運算符,是一種拷貝并交換的技術
//example18.cpp
class Person
{
friend void swap(Person &a, Person &b);
public:
int age;
string name;
Person &operator=(Person person);
Person(const int &age, const string &name) : age(age), name(name) {}
};
// person為使用合成拷貝構造函數值複制
Person &Person::operator=(Person person)
{
swap(*this, person); //二者内容交換
return *this;
}
// Person的swap行為
inline void swap(Person &a, Person &b)
{
a.age = b.age;
a.name = b.name;
}
int main(int argc, char **argv)
{
Person person1(19, "me");
Person person2 = person1;
cout << person2.age << " " << person2.name << endl; // 19 me
return 0;
}
對象移動
什麼是對象移動,也就是将對象移動到某處,即複制,但複制後就将原來的進行對象銷毀了
标準庫函數
std::move
,标準庫容器、string、shared_ptr類即支援移動也支援拷貝,IO類和unique_ptr類可以移動但不能拷貝
//example19.cpp
#include <iostream>
#include <utility>
#include <string>
int main(int argc, char **argv)
{
using namespace std;
string a1 = "hello";
string a2 = std::move(a1);
cout << a1 << endl; // nothing
cout << a2 << endl; // hello
int b1 = 999;
int b2 = std::move(b1); // int不是對象是基本資料類型不适用
cout << b1 << endl; // 999
cout << b2 << endl; // 999
return 0;
}
右值引用
什麼是右值引用,右值引用為支援移動操作而生,右值引用就是必須綁定到右值的引用,使用&&而不是&來獲得右值引用
左值與右值的聲明周期,左值有持久的狀态直到變量聲明到上下文切換記憶體釋放,右值要麼是字面量或者求值過程中的臨時對象
右值引用特性:
- 所引用的對象将要被銷毀
- 該對象沒有其他使用者
- 無法将右值引用綁定到右值引用
//example20.cpp
int main(int argc, char **argv)
{
int num = 666;
int &ref = num; //引用
// int &&refref = num; //錯誤:不能将右值引用綁定到左值上
// int &ref1 = num * 42; //錯誤:i*42為右值
const int &ref2 = num * 42; // const引用可綁定到右值上
int &&refref1 = num * 10; // 右值引用可以綁定在右值上
cout << refref1 << endl; // 6660
refref1 = 999;
cout << refref1 << endl; // 999 而且與使用普通變量沒什麼差別
// int &&refref2 = refref1; //錯誤:無法将右值引用綁定到左值
return 0;
}
move與右值引用
雖然不能将右值引用綁定在左值上,但可以通過std::move來實作
//example21.cpp
int main(int argc, char **argv)
{
int num = 999;
// int &&rr1 = num; //錯誤 無法将右值引用綁定到左值
string stra = "hello";
string &&straRef = std::move(stra);
cout << stra << endl; // hello
cout << straRef << endl; // hello
stra = "world";
cout << straRef << endl; // world
//可見straRef綁定定在了stra上
string a = "world";
string b = std::move(a);
// move函數的表現根據等号左側的類型的不同随之行為也不同
cout << a << endl; // nothing
cout << b << endl; // world
return 0;
}
右值引用做參數
右值引用的最大貢獻就是将臨時變量的聲明周期延長,減少臨時變量的頻繁銷毀,記憶體的利用效率也會變高,當右值表達式被處理後結果存放在會塊臨時記憶體空間,右值引用指向它,則可以利用,直到指向它的右值引用全部被銷毀,記憶體才會被釋放
//example22.cpp
class Person
{
public:
string name;
Person(const string &name) : name(name)
{
cout << "string &name" << endl;
}
Person(string &&name) : name(name)
{
cout << "string &&name" << endl;
}
};
// const引用與右值引用重載時 傳遞右值時 右值引用的優先級高
int main(int argc, char **argv)
{
//建立臨時變量"hello"
Person person1("hello"); // string&&name
string s = "world";
Person person2(s); // string &name
return 0;
}
右值和左值引用成員函數
在舊标準中,右值可以調用相關成員函數與被指派
//example23.cpp
int main(int argc, char **argv)
{
string hello = "hello";
string world = "world";
cout << (hello + world = "nice") << endl; // nice 右值被指派
return 0;
}
&左值限定符
怎樣限定指派時右邊隻能是可修改的左值指派,引入了引用限定符(reference qualifier),使得方法隻有對象為左值時才能被使用
//example24.cpp
#define USE_LIMIT
class Person
{
public:
string name;
#ifdef USE_LIMIT
Person &operator=(const string &) &; //引用限定符 等号左側必須為可修改的左值
#else
Person &operator=(const string &);
#endif
Person(string &&name) : name(name)
{
}
inline void print()
{
cout << this->name << endl;
}
};
#ifdef USE_LIMIT
Person &Person::operator=(const string &name) & //引用限定
#else
Person &Person::operator=(const string &name)
#endif
{
this->name = name;
return *this;
}
Person func()
{
return Person("me");
}
int main(int argc, char **argv)
{
func() = "hello"; // func()傳回右值
//當define USE_LIMIT時發生錯誤
//沒有define USE_LIMIT時不會發生錯誤
return 0;
}
const與&左值限定符
一個方法可以同時用const和引用限定,引用限定必須在const之後
//example25.cpp
class Person
{
public:
int age;
string name;
Person(const int &age = 19, const string &name = "me") : age(age), name(name)
{
}
void print() const &
{
cout << age << " " << name << endl;
}
};
//func傳回右值
Person func()
{
return Person(19, "she");
}
int main(int argc, char **argv)
{
func().print();
//當print不是const&時報錯,例如隻有引用限定符&,隻有const不報錯
//很雞肋沒什麼卵用
//當有const時 &限定作用消失了
return 0;
}
&&右值引用限定符
可以使用
&&
進行方法重載,使其為可改變的右值服務
當一個方法名字相同 函數參數清單相同時 有一個有引用限定,全部都應該有引用限定或者全部都沒有
//example26.cpp
class Foo
{
public:
Foo sort() &&;
Foo sort() const &;
//當一個方法名字相同 函數參數清單相同時 有一個有引用限定
//全部都應該有引用限定或者全部都沒有
};
Foo Foo::sort() &&
{
cout << "&&" << endl;
return *this;
}
Foo Foo::sort() const &
{
cout << "const &" << endl;
return *this;
}
// func傳回右值
Foo func()
{
return Foo();
}
int main(int argc, char **argv)
{
Foo foo1;
foo1.sort(); // const &
func().sort(); //&&
//如果沒有定義Foo Foo::sort() && 二者都會調用 Foo Foo::sort() const &
return 0;
}
移動構造函數和移動指派運算符
資源移動執行個體,嫖竊其他對象的記憶體資源,與拷貝構造函數類似,移動構造函數第一個參數也是引用類型,但隻不過是右值引用,任何額外參數必須有預設實參
//example27.cpp
class Person
{
public:
int *age;
string *name;
Person(const int &age, const string &name) : age(new int(age)), name(new string(name))
{
}
//移動操作不應抛出任何異常
Person(Person &&person) noexcept //”盜竊“資源 這是個移動構造函數不是 拷貝構造函數
{
delete age, delete name;
age = person.age;
name = person.name;
person.age = nullptr;
person.name = nullptr;
}
void print();
};
void Person::print()
{
if (age && name)
{
cout << *age << " " << *name;
}
cout << endl;
}
int main(int argc, char **argv)
{
Person person1(19, "me");
Person person2 = std::move(person1);
person1.print(); // nothing
person2.print(); // 19 me
return 0;
}
移動指派運算符
與拷貝類似,可以也可以重載指派運算符來實作對象的移動功能
//example28.cpp
class Person
{
public:
int *age;
string *name;
Person(const int &age, const string &name) : age(new int(age)), name(new string(name))
{
cout << "Person(const int &age, const string &name)" << endl;
}
Person &operator=(Person &&person) noexcept;//移動指派運算符
Person(const Person &person) : age(person.age), name(person.name)
{
cout << "Person(const Person &person)" << endl;
}
void print();
};
Person &Person::operator=(Person &&person) noexcept
{
cout << "Person &Person::operator=(Person &&person)" << endl;
if (&person != this)
{
delete age, delete name;
age = person.age;
name = person.name;
person.age = nullptr;
person.name = nullptr;
}
return *this;
}
void Person::print()
{
if (age && name)
{
cout << *age << " " << *name;
}
cout << endl;
}
//傳回右值
Person func()
{
return Person(19, "she"); // Person(const int &age, const string &name) 2
}
int main(int argc, char **argv)
{
Person person2(18, "oop"); // Person(const int &age, const string &name) 1
person2 = func(); // Person &Person::operator=(Person &&person)
person2.print(); // 19 she
Person person1 = std::move(person2);//person2移動到person1
cout << *person1.age << " " << *person2.name << endl; // 19 she
return 0;
}
合成的移動操作
隻有當一個類沒有定義任何自己版本的拷貝控制成員時,且所有資料成員都能進行移動構造或移動指派時,編譯器才會合成移動構造函數或移動指派運算符
當一定義了拷貝控制成員,如自定義了拷貝構造拷貝拷貝指派時,将不會提供合成的移動操作
//example29.cpp
class X
{
public:
int i; //内置類型可以移動
string s; // string定義了自己的移動操作
};
class HasX
{
public:
X member; // X有合成的移動操作
};
int main(int argc, char **argv)
{
X x1;
x1.i = 100;
x1.s = "me";
cout << x1.i << " " << x1.s << endl; // 100 me
// X移動
X x2 = std::move(x1);
cout << x2.i << " " << x2.s << endl; // 100me
cout << x1.i << " " << x1.s << endl; // 100 nothing
// HasX移動
HasX hasx;
hasx.member.i = 99;
hasx.member.s = "me";
HasX hasx1 = std::move(hasx);
cout << hasx1.member.i << " " << hasx1.member.s << endl; // 99 me
return 0;
}
本質上move的使用就是調用了拷貝構造函數,但拷貝構造是值拷貝還是指針拷貝有我們自己定義
//example30.cpp
class Y
{
public:
int age;
Y() = default;
Y(const Y &y) //拷貝構造 則 Y沒有合成的移動操作
{
this->age = age;
};
// Y(Y &&y)
// {
// age = y.age;
// y.age = 0;
// }
};
class HasY
{
public:
HasY() = default;
Y member;
};
int main(int argc, char **argv)
{
HasY hasy;
hasy.member.age = 999;
HasY hasy1 = std::move(hasy); //因為Y沒有移動操作
cout << hasy1.member.age << endl; //亂碼 hasy為一個新對象,為Y添加自定義移動構造函數則輸出999
cout << "end" << endl; // end
return 0;
}
拷貝構造與移動構造的比對
當一個類既有移動構造函數,也有拷貝構造函數,當我們使用哪一個,會根據函數比對規則來确定
//example31.cpp
class Person
{
public:
Person() = default;
Person(const Person &person)
{
cout << "Person(const Person &person)" << endl;
}
Person(Person &&person)
{
cout << "Person(Person &&person)" << endl;
}
};
int main(int argc, char **argv)
{
Person person1;
Person person2(person1); // Person(const Person &person)
const Person person3;
Person person4(person3); // Person(const Person &person)
//而移動構造隻接受右值
Person person5 = std::move(person4); // Person(Person &&person)
return 0;
}
要注意的是,當有拷貝構造函數沒有移動構造函數時,右值也将被拷貝
//example32.cpp
class Person
{
public:
int age;
string name;
Person() = default;
Person(const Person &person) : age(person.age), name(person.name)
{
cout << " Person(const Person &person)" << endl;
}
};
int main(int argc, char **argv)
{
Person person1;
// std::move的作用就是将person1作為右值傳遞
Person person2(std::move(person1)); // Person(const Person &person)
//當存在移動構造時則會優先使用移動構造
return 0;
}
拷貝并交換指派運算符和移動操作
當定義了移動構造函數,且定義了指派運算符,但無定義移動指派方法,則将一個右值賦給左值時,将會先使用移動構造函數構造新對象,然後将新對象指派給原左值,類似地隐含了移動指派
//example33.cpp
class Person
{
public:
int age;
string name;
Person() = default;
Person(const int &age, const string &name) : age(age), name(name){};
//移動構造函數
Person(Person &&p) noexcept : age(p.age), name(p.name)
{
cout << "Person(Person &&p)" << endl;
p.age = 0;
p.name = "";
}
//拷貝構造
Person(const Person &p) : age(p.age), name(p.name)
{
cout << "Person(const Person &p)" << endl;
}
//指派運算符 也是 移動指派運算符 也是拷貝指派運算符
Person &operator=(Person p)
{
cout << "Person &operator=(Person p)" << endl;
age = p.age;
name = p.name;
return *this;
}
};
int main(int argc, char **argv)
{
Person person1(19, "me"); //構造函數
//顯式調用移動構造函數
Person person2(std::move(person1)); // Person(Person &&p)
cout << person1.age << " " << person1.name << endl; // 0 nothing
Person person3(19, "me"); //構造函數
Person person4; //預設構造函數
person4 = std::move(person3); //先使用移動構造函數生成新對象 将新對象指派給person4
// Person(Person &&p) Person &operator=(Person p)
cout << person4.age << " " << person4.name << endl; // 19 me
return 0;
}
移動疊代器
移動疊代器解引用傳回一個指向元素的右值引用
//example34.cpp
int main(int argc, char **argv)
{
vector<string> vec = {"aaa", "bbb"};
auto iter = make_move_iterator(vec.begin());
// auto std::move_iterator<std::vector<std::string>::iterator>
allocator<string> allocat;
string *ptr = allocat.allocate(10);
uninitialized_copy(make_move_iterator(vec.begin()), make_move_iterator(vec.end()), ptr);
cout << vec[0] << " " << vec[1] << endl; //空字元串
cout << ptr[0] << " " << ptr[1] << endl; // aaa bbb
//可見使用移動疊代器進行了移動操作
return 0;
}