C++11的初始化列表
基本概念:https://www.cnblogs.com/LuckCoder/p/8467656.html
class A {
public:
explicit A(int value) : m_value(value) {}
private:
int m_value;
};
static void newVersionConstruct() {
int avector[] = { 1, 2, 3 };
std::vector<int> bv = { 1, 2, 3 };
A a(10);
A b{ 3 };
//bool類型轉成int類型的精度沒有損失。true會轉化成0,false轉成1.
//因爲沒有精度損失,所以這種轉化是允許的
A c(true);
A d{ true };
//A e(1.1);
//A f{1.0};
}
左值引用和右值引用
//由於傳入的參數類型是const引用,因此傳入左值或者右值都是可以的
void printInfo(const int& a) {
std::cout << a << std::endl;
}
static void leftRefAndRightRef() {
int a = 10;
int& refA = a; //引用類似於別名
const int& constRefA = a; //const引用可以防止對原數據的修改
std::cout << " a " << a << " ref of a " << refA << " const ref a "
<< constRefA << std::endl;
// this is a error
//int& refB = 10;
const int& constRefB = 10; //const引用可以指向一個右值
printInfo(constRefB);
printInfo(10);
printInfo(a);
printInfo(refA);
std::cout << "different version const ref " << constRefB << std::endl;
auto&& rrA = 10; //右值引用
const int&& crrB = 20;
rrA = 30; //可以通過右值引用改變一個右值的大小
std::cout << " right ref of b " << rrA << " const right ref b "
<< crrB << std::endl;
int b = 20;
// 下面兩句是錯誤的,編譯器不支持隱式的將左值轉成右值
//int&& rrB = b;
//const int&& crrB = b;
//顯示的將左值轉成右值
int&& newRRB = std::move(b);
const int&& cNewRRB = std::move(b);
//b的地址和newRRB的地址和cNewRRB的地址相同
std::cout << " b " << b << " right ref of b " << newRRB << " const right ref b "
<< cNewRRB << std::endl;
std::cout << "address " << &b << " ref " << &newRRB << " c ref " << &cNewRRB << std::endl;
}
構造函數和析構函數的經典應用——智能指針
引言
//getName和getName2兩個函數都只返回一個指針,這種設計存在一定的的缺陷:
//用戶不明確指針指向的內存是否屬於自己管理的範圍
//多線程的環境下,該函數返回的指針只能當場使用,因爲其他線程可以修改valueGroup的值
const char* getName() {
static char valueGroup[1000];
// do some thing
return valueGroup;
}
//多線程下返回的指針所指的值可以一直用,但是要記得手動釋放內存
const char* getName2() {
char* value = (char*)malloc(1000);
// do something
return value;
}
//下面的函數可以解決上面函數的問題:返回的指針所指的那塊內存是否需要程序員去手動釋放
char* getName(char* v, size_t bufferSize) {
// do something
return v;
}
對於堆上的內存,可以使用malloc和free或者new和delete進行控制。但是如果不注意,還是會發生內存泄漏。如下:
void badThing() {
//拋出異常
throw 1;
}
void versionOne() {
//void*
int* ageC = (int*)malloc(sizeof(int));
//判斷是否生成成功
if (ageC) {}
free(agcC);
char* c = (char*)malloc(100);
free(c);
int* age = new int(25);
int* height = new int(160);
//當badThing拋出異常之後,即使下面寫了delete,age和height所指向的內存依然泄漏了
badThing();
std::cout << "VersionOne: your age is " << *age << ", and your height is "
<< *height << std::endl;
// if forget, TOO BAD thing will happen
delete height;
delete age;
}
當函數badThing()拋出異常之後,即使下面寫了delete,age和height所指向的內存依然泄漏了。
如果我們寫了一個類,用來控制內存的產生和釋放,如下,在多線程的情況下,還是會出現一些問題:
class SafeIntPointer {
public:
explicit SafeIntPointer(int v) : m_value(new int(v)), m_used(1) {}
~SafeIntPointer() {
//下面的三句代碼存在問題:在多線程情況下,可能會出現
//多個線程同時滿足條件if (m_used <= 0)的情況
//此時就會出現問題m_value所指向的資源會被重複釋放
m_used--;
if (m_used <= 0)
delete m_value;
}
// copy
// operator =
int* get() { return m_value; }
private:
int m_used; //記錄使用者人數,當該值爲0時,釋放資源
int* m_value;
};
爲此,C++11推出用於管理內存的智能指針——shared_ptr
void versionTwo() {
std::shared_ptr<int> age(new int(28));
std::shared_ptr<int> height(new int(160));
std::cout << "VersionTwo: your age is " << *age << ", and your height is "
<< *height << std::endl;
// 不需要做任何額外的事情,內存會自動的釋放掉
}
智能指針總體概述
- auto_ptr 不推薦使用
- shared_ptr 通過引用來實現指針共享
- weak_ptr 和shared_ptr搭配使用
- unique_ptr 一個指針只能有一個用戶使用
- enable_shared_from_this // CRTP
shared_ptr
基礎
- 多個shared_ptr可以共享同一個對象,對象的最末一個擁有者有責任銷燬對象,並清理與該對象相關的所有資源。
- 一般而言,shared pointer並非線程安全
#include<iostream>
#include<string>
#include<vector>
#include<memory> //for shared_ptrshared_ptr
using namespace std;
int main(){
shared_ptr<string> pNico(new string("nico"),
[](string *p){
cout<<"delete"<<*p<<endl;
delete p;
});
shared_ptr<string> pJutta(new string("jutta"));
//shared_ptr<string> pJutta = new string("jutta"); // error,shared_ptr's constructor is explicit
//shared_ptr<string> pJutta{new string("jutta")}; // ok
//shared_ptr<string> pJutta = make_shared<string>("nico"); // ok
(*pNico)[0] = 'N';
pJutta->replace(0, 1, "J");
vector<shared_ptr<string>> whoMadeCoffee;
whoMadeCoffee.push_back(pJutta);
cout<<"use_count:"<<whoMadeCoffee[0].use_count()<<endl;
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
for(auto ptr : whoMadeCoffee){
cout<<*ptr<<" ";
}
cout<<endl;
*pNico = "Nicoai";
for(auto ptr : whoMadeCoffee){
cout<<*ptr<<" ";
}
cout<<endl;
cout<<"use_count:"<<whoMadeCoffee[0].use_count()<<endl;
pNico =nullptr;
cout<<"use_count:"<<whoMadeCoffee[2].use_count()<<endl;
whoMadeCoffee.resize(2);
cout<<"use_count:"<<whoMadeCoffee[2].use_count()<<endl;
return 0;
}
運行結果:
shared_ptr的使用例子
/*
確保“指向某臨時文件”最後一個reference被銷燬時,該文件被移除
*/
#include<fstream>
#include<string>
#include<vector>
#include<memory> //for shared_ptr
#include<cstdio> //for remove()
using namespace std;
class FileDelter{
private:
string filename;
public:
FileDelter(const string& fn):
filename(fn){}
void operator()(std::ofstream* fp){
fp->close();
std::remove(filename.c_str());
}
};
int main(){
//create and open temporary file
std::shared_ptr<ofstream>fp(new std::ofstream("tmpfile.txt"),
FileDelter("tmpfile.txt"));
}
// 使用shared_ptr打開共享內存
#include<memory>
#include<sys/mman.h> //for shared memory
#include<fcntl.h>
#include<unistd.h>
#include<cstring> //for strerror()
#include<cerrno> //for errno
#include<string>
#include<iostream>
class shared_ptr_shared_memory {
public:
void operator()(int* p){
std::cout<<"unlink/tmp1234"<<std::endl;
if(shm_unlink("/tmp1234")!=0){
std::cerr<<"OOPS: shm_unlink() failed" <<std::endl;
}
}
};
std::shared_ptr<int> get_shared_memory(int num){
void* mem;
int shmfd = shm_open("/tmp1234", O_CREAT|O_RDWR, S_IRWXU|S_IRWXG);
if(shmfd<0){
throw std::string(strerror(errno));
}
if(ftruncate(shmfd, num*sizeof(int))==-1){
throw std::string(strerror(errno));
}
mem = mmap(nullptr, num*sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED, shmfd, 0);
if(mem ==MAP_FAILED){
throw std::string(strerror(errno));
}
return std::shared_ptr<int>(static_cast<int*>(mem), shared_ptr_shared_memory());
}
int main(){
std::shared_ptr<int>smp(get_shared_memory(100));
for(int i=0;i<100;++i){
//shared_ptr 沒有提供指針運算和operator[],因此如果想訪問內存,
//需要使用get()獲得被shared_ptr包裹(wrapped)的內部指針
smp.get()[i] = i*42; //same as (&*smp)[i] = i*42;
}
//dealwith shared memory somewhere else
std::cout<<"<return>"<<std::endl;
std::cin.get();
//release shared memnry here
smp.reset();
}
class Object {
public:
Object(int id) : m_id(id) { std::cout << "init obj " << m_id << std::endl; }
~Object() { std::cout << "bye bye " << m_id << std::endl; }
int id() const { return m_id; }
private:
int m_id;
};
typedef std::shared_ptr<Object> ObjectPtr;
//多線程情況下,推薦以值的形式傳入智能指針
void print(ObjectPtr obj) {
//打印出來是3,因爲作爲值傳入,會對智能指針進行一次拷貝
std::cout << "count " << obj.use_count() << " id " << obj->id() << std::endl; // 3
}
void printRef(const ObjectPtr& obj) {
//打印出來是2,因爲作爲引用傳入
std::cout << "ref count " << obj.use_count() << " id " << obj->id()
<< std::endl; // 2
}
//以該類本身作爲參數傳入也是可以的。否則作爲只能通過只能指針作爲參數
void print(const Object& obj) {}
void interfaceOfSharedPtr() {
ObjectPtr null;
std::cout << "ref count is " << null.use_count() << std::endl; // 0
ObjectPtr obj(new Object(1));
std::cout << "ref count is " << obj.use_count() << std::endl; // 1
ObjectPtr obj2(obj); //通過拷貝,共享智能指針。obj和obj2指向同一份資源
std::cout << "ref count is " << obj.use_count() << std::endl; // 2
std::cout << "ref count obj2 is " << obj2.use_count() << std::endl; // 2
ObjectPtr obj3 = obj; //通過拷貝,共享智能指針。
std::cout << "ref count is " << obj.use_count() << std::endl; // 3
obj2.reset(); // reset means what? reset指的是obj2不再被使用
obj2 = nullptr; //這句和上面這句的功能是相同的
std::cout << "ref count is " << obj.use_count() << std::endl; // 2
ObjectPtr obj4;
obj3.swap(obj4); //交換obj4和obj3所指向的資源
std::swap(obj3, obj4); //這句和上一句的功能相同。
std::cout << "ref count is " << obj.use_count() << std::endl; // 2
//通過get獲取obj指向的資源。p是一個裸指針,可以操作obj所指向的資源
//get函數不到萬不得已不要使用,因爲如果p調用了delete,會發生什麼事情呢
//get是爲了第三方函數的接口所準備的,不到萬不得已不要使用
auto p = obj.get();
if(p) std::cout << "id is " << p->id() << std::endl;
if(obj) { // operator bool 判斷只能指針是否爲空。智能指針重載了“->”和“operator*”
std::cout << "p id is " << obj->id() << std::endl; // operator ->
std::cout << "ref id is " << (*obj).id() << std::endl; // operator *
}
obj4 = nullptr;
//if(obj.unique()) 判斷是否只有一個人在使用他
//if(obj.use_count() == 1) use_count可能會有意想不到的效率損失
//因爲智能指針可能在多線程下使用,use_count要處理多線程的情況。
std::cout << "only one hold ptr " << obj.unique() << std::endl;
print(obj); //可以把智能指針作爲值進行傳入。傳入後會對智能指針進行一次拷貝
std::cout << "ref count is " << obj.use_count() << std::endl; // 2
printRef(obj); //可以把只能指針作爲引用傳入
print(*obj); //以該類本身作爲參數傳入也是可以的
}
void deleterOfObject(Object* obj) {
if (obj)
std::cout << "delete obj " << obj->id() << std::endl;
delete obj;
}
void useDeleter() {
//傳入的參數是裸指針以及處理指針的函數
ObjectPtr obj(new Object(2), deleterOfObject);
ObjectPtr obj2 = obj;
}
有些情況下shared_ptr無法處理
當存在彼此依賴關係的時候,兩個類都在等待對方釋放資源之後再釋放資源。因此造成了內存泄漏。例子如下:
#include<iostream>
#include<string>
#include<vector>
#include<memory>
using namespace std;
class Person{
public:
string name;
shared_ptr<Person>mother;
shared_ptr<Person>father;
vector<shared_ptr<Person>>kids;
Person(const string& n,
shared_ptr<Person>m=nullptr,
shared_ptr<Person> f = nullptr)
:name(n), mother(m), father(f){}
~Person(){
cout<<"delete"<<name<<endl;
}
};
shared_ptr<Person> initFamily(const string&name){
shared_ptr<Person> mom(new Person(name+"'s mom"));
shared_ptr<Person>dad(new Person(name +"'s dad"));
shared_ptr<Person> kid(new Person(name, mom, dad));
mom->kids.push_back(kid);
dad->kids.push_back(kid);
return kid;
}
int main(){
shared_ptr<Person> p = initFamily("nico");
cout<<"nico's family exists"<<endl;
cout<<"-nico is shared "<<p.use_count()<<" times"<<endl;
cout<<"-name of 1st kid of nico's mom: "
<<p->mother->kids[0]->name <<endl;
p=initFamily("jim");
cout<<"jim's family exists"<<endl;
}
運行結果:
代碼中 p指向家挺中的最密一個handle,而在內部,每個person對象都有着reference從kid指向父母以及反向指向。因此,在p被賦值之前,nico被共享三次。
當我們釋放受傷最末一個指向該家挺的handle——也許是對p指派一個新person或者一個nullptr,也許是main()結束時離開了p作用域——沒有任何person會被釋放,因爲他們都至少被一個shared pointer指向。於是每個person的析構函數從未被調用。
如果存在彼此之間的循環引用關係,則shared_ptr不能釋放掉彼此應該釋放的資源。爲了打破侷限性,引入weak_ptr。
weak_ptr
- weak_ptr可以打破循環引用。weak_ptr可能管理了一份資源,當weak_ptr管理的資源被外部其他智能指針管理,則該資源是有效的,否則,weak_ptr管理的資源是無效的。
- weak_ptr可以安全的實現“共享但不擁有”的需求。一旦最後一個擁有該對象的shared_ptr失去了擁有權,任何weak_ptr都會自動成空。這樣可以有效的避免”訪問已被釋放的數據“。
weak_ptr的基本用法
void sharedPtrWithWeakPtr() {
//typedef std::shared_ptr<Object> ObjectPtr;
ObjectPtr obj(new Object(1));
typedef std::weak_ptr<Object> WeakObjectPtr;
WeakObjectPtr weakObj2;
WeakObjectPtr weakObj(obj);
WeakObjectPtr weakObj3(obj);
//由於只有一個shared_ptr在管理這份資源。哪怕存在兩個weak_ptr在監聽這份資源
std::cout << "obj use count is " << obj.use_count() << std::endl; // 1
{
//希望使用weak_ptr時,需要對weak_ptr進行轉換。
//調用lock函數,當外部引用不小於1時,則返回一個有效指針
auto p = weakObj.lock(); // auto === ObjectPtr
if(p) {
//p.unique()的結果是false。因爲p.use_count() >= 2
//因爲當weak_ptr管理的那份資源在外部還被其他智能指針所管理的時候,
//weak_ptr所管理的資源纔是有效的。因此 p.use_count() >= 2
std::cout << p.unique() << std::endl;
// do what you want to do
} else {
//說明這份資源已經沒有外部智能指針進行管理了
//此時返回的p是一個空指針
//說明weak_ptr所管理的資源已經被析構了
}
}
//調用reset函數放棄對Object1的管理。現在管理Object2
obj.reset(new Object(2));
{
//weakObj管理的是Object1,然而已經沒有shared_ptr管理Object1了,
//此時調用lock函數會返回一個空指針
auto p = weakObj.lock();
if(p) {
assert(false);
// do what you want to do
} else {
}
}
//此時weak_tr又指向了和obj相同的資源
weakObj = obj;
//expired函數判斷資源是否過期。
if(weakObj.expired()) {
//說明weak_ptr指向的資源已經過期
} else {
}
}
現在來看一下如何使用weak_ptr去解決shared_ptr無法解決的循環調用問題
#include<iostream>
#include<string>
#include<vector>
#include<memory>
using namespace std;
class Person{
public:
string name;
shared_ptr<Person>mother;
shared_ptr<Person>father;
vector<weak_ptr<Person>>kids; //weak_ptr
Person(const string& n,
shared_ptr<Person>m=nullptr,
shared_ptr<Person> f = nullptr)
:name(n), mother(m), father(f){}
~Person(){
cout<<"delete"<<name<<endl;
}
};
shared_ptr<Person> initFamily(const string&name){
shared_ptr<Person> mom(new Person(name+"'s mom"));
shared_ptr<Person>dad(new Person(name +"'s dad"));
shared_ptr<Person> kid(new Person(name, mom, dad));
mom->kids.push_back(kid);
dad->kids.push_back(kid);
return kid;
}
int main(){
shared_ptr<Person> p = initFamily("nico");
cout<<"nico's family exists"<<endl;
cout<<"-nico is shared "<<p.use_count()<<" times"<<endl;
cout<<"-name of 1st kid of nico's mom: "
<<p->mother->kids[0].lock()->name <<endl; //differ with shared_ptr: p->mother->kids[0]->name
p=initFamily("jim");
cout<<"jim's family exists"<<endl;
}
運行結果
如果不確定隱身於weak pointer背後的對象是否仍舊存貨,你有一下幾個選擇:
1. 調用expired(),它會在weak_ptr不在共享對象時返回true。折等同於檢查use_count()是否位0,但是速度較快。
2. 可以使用相應的shared_ptr構造函數明確將weak_ptr轉換位shared_ptr。如果被指對象以及該不存在了,該構造函數明確將會拋出一個bad_weak_ptr異常。
3. 調用use_count(),詢問相應對象的擁有者數量。如果返回0表示不存在任何有效對象。use_count()並不是很有效率,因此通常並不是很有效率。
enable_shared_from_this 和weakptr輔助sharedptr
#include <cassert>
#include <memory>
#include <iostream>
// auto_ptr
// shared_ptr
// enable_shared_from_this // CRTP
// weak_ptr
// unique_ptr
void sharedPtrNotice();
class Parent;
typedef std::shared_ptr<Parent> ParentPtr;
typedef std::weak_ptr<Parent> WeakParentPtr;
// public std::enable_shared_from_this<Child>歧義模板遞歸參數模式 CRTP
class Child : public std::enable_shared_from_this<Child> {
public:
WeakParentPtr father;
~Child();
Child();
void checkRelation();
};
typedef std::shared_ptr<Child> ChildPtr;
typedef std::weak_ptr<Child> WeakChildPtr;
class Parent : public std::enable_shared_from_this<Parent> {
public:
WeakChildPtr son;
~Parent();
Parent();
void checkRelation();
};
void handleChildAndParentRef(const Parent& p, const Child& c) {
auto cp = c.father.lock();
auto pc = p.son.lock();
if(cp.get() == &p && pc.get() == &c) {
std::cout << "right relation\n";
} else {
std::cout << "oop!!!!!\n";
}
}
//傳入const智能指針的引用是爲了減少拷貝次數
void handleChildAndParent(const ParentPtr& p, const ChildPtr& c) {
//assert(c); 可以用於驗證傳入的是否有效
//如果傳入的是空指針,則會拋出異常
//weakptr通過lock函數獲得shared_ptr.
auto cp = c->father.lock();
auto pc = p->son.lock();
if(cp == p && pc == c) {
std::cout << "right relation\n";
} else {
std::cout << "oop!!!!!\n";
}
}
Child::Child() { std::cout << "hello child\n";}
Parent::Parent() { std::cout << "hello parent\n";}
Child::~Child() { std::cout << "bye child\n";}
Parent::~Parent() { std::cout << "bye parent\n";}
//調用handleChildAndParent
void Parent::checkRelation() {
//weakptr不能直接用,要通過lock函數驗證一下
//如果son不是weakptr,則下面的過程可以改寫成
//handleChildAndParent(Parent的智能指針,son);
auto ps = son.lock();
if(ps) {
// this
handleChildAndParent(shared_from_this(), ps);
//下面的寫法是錯誤的,執行完之後,會調用一次析構函數。
//從而導致析構函數被調用兩次
/*
ParentPtr p(this);
handleChildAndParent(p, ps);
*/
}
std::cout << "after call checkRelation\n";
}
void Child::checkRelation() {
// we call handleChildAndParent
}
unique_ptr——在某個特定的時刻,只會有一個unique_ptr來管理這一個指針
unique_ptr是一種在異常發生時可幫助避免資源泄露的smart pointer。這個smart pointer實現了獨佔式擁有的概念。
unique_ptr的接口使用例子
class Object {
public:
Object(int id) : m_id(id) { std::cout << "init obj " << m_id << std::endl; }
~Object() { std::cout << "bye bye " << m_id << std::endl; }
int id() const { return m_id;}
private:
int m_id;
};
typedef std::unique_ptr<Object> UniqueObjectPtr;
void print(const UniqueObjectPtr& obj) {}
void transfer(UniqueObjectPtr obj) {
std::cout << obj->id() << std::endl;
}
void uniquePtr() {
UniqueObjectPtr obj(new Object(1));
auto p = obj.get(); // 獲得指針的原生接口
//判斷原生指針是否存在
if(p) {}
// better 也可以拿來直接用,判斷原生指針存在不存在
if(obj) {}
// operator -> *
std::cout << p->id() << obj->id() << (*obj).id() << std::endl;
print(obj);
p = obj.release();
delete p;
//uniquePtr有兩種reset形式,如下:
obj.reset();
obj.reset(new Object(2));
/* 上面這句等價於:
先把原有的資源釋放掉obj.reset();
然後再去管理新的指針
*/
//在unique_ptr中
// UniqueObjectPtr(const UniqueObjectPtr&) 這種形式的拷貝構造函數是不存在的
// UniqueObjectPtr(UniqueObjectPtr&&) 存在這種形式的拷貝構造函數
//因此可以通過傳入右值來
//transfer傳入的是一個unique_ptr的“值”。
//由於unique_ptr的特性是同一時刻只能有一個unique_ptr來管理這份資源
//因此調用transfer時,可以傳入一個右值引用,這樣會調用unique_ptr的傳入右值的拷貝構造函數
transfer(std::move(obj));
assert(obj == nullptr);
//std::cout << obj->id() << std::endl;
obj.reset(new Object(4));
//將unique_ptr轉化成shared_ptr
ObjectPtr sharedObj(std::move(obj));
assert(obj == nullptr);
}
使用智能指針需要注意的坑
void sharedPtrNotice() {
// 前提:絕對不要自己手動的管理資源
/*因此下面的代碼是不好的:
第一種形式:
int* a = new int(10);
delete a;
第二種形式:
int* b = malloc(sizeof(int));
if (b) {
free(b);
}
*/
//第一點:
//一個裸的指針不要用兩個shared_ptr管理, unique_ptr也是一樣。
//會出現資源的重複釋放
auto pObj = new Object(1);
ObjectPtr obj(pObj);
ObjectPtr obj2(pObj);
//第二點:
// 用weak_ptr打破循環引用,parent 和 child
// 當需要在類的內部接口中,如果需要將this作爲智能指針來使用的話,需要
// 用該類派生自enable_shared_from_this
//第三點:
// 使用shared_ptr作爲函數的接口,如果有可能有const shared_ptr&的形式
// 多線程模式下使用shared_ptr需要注意的事項
//第四點:
// shared_ptr、 weak_ptr 和裸指針相比,空間上會大很多,並且效率上會有影響。
//尤其是在多線程模式下
//對於下面的過程,裸指針的操作是原子的。而智能指針需要先做兩個引用計數加1以及一個資源的拷貝。
//在多線程情況下,爲了保證拷貝是原子安全的,還要做很多額外的操作
ObjectPtr obj3(new Object(2));
ObjectPtr obj4 = obj3;
//第五點:
//可以通過如下方法來構造智能指針
ObjectPtr obj5 = std::make_shared<Object>(3);
// ObjectPtr obj5(new Object(3)); 這句和上面那句是等價的,但是上面那句的效率更好
//第六點:
//enable_shared_from_this中有一個函數——shared_from_this()(他不能在構造函數和析構函數中使用)和構造析構函數一樣,
// 某些情況下,會出現內存不會降的問題。尤其是使用weak_ptr來處理循環引用的問題
//當某些資源已經失效了,但是weak_ptr在引用這個資源,因此該資源不能被釋放,內存就因此提升了
//第七點:
// 如果有可能,優先使用類的實例,其次萬不得已使用std::unique_ptr,
// 萬不得已使用std::shared_ptr
Object obj6(6);
}