我最不喜歡循規(guī)循矩,雖然是讓寫筆記,照著老師的ppt抄一遍有什么意思。所以我還是喜歡寫自己的東西。
最近我有個(gè)怪癖,愛把所有帶指針的類都改造成使用智能指針來控制資源分配和回收。因?yàn)槲艺J(rèn)為既然是c++11標(biāo)準(zhǔn)出的應(yīng)該是可以頂替99標(biāo)準(zhǔn),更安全更先進(jìn)的用法,為什么不用呢?結(jié)果在這兩周侯捷老師的c++課上的例子的智能指針改寫上吃了苦頭,也領(lǐng)悟到什么時(shí)候該用智能指針,什么時(shí)候不該用。離提交作業(yè)的日期只剩兩天不到,有空的話我會(huì)將我對(duì)Date類,Rectangle類的改寫也在這里講一下。
我這里目的并不是講解智能指針,所以講的并不細(xì)。
什么是智能指針:
智能指針就是通過模板實(shí)現(xiàn)的對(duì)普通指針的一種封裝,我偏愛用shared_ptr指針。通過引用計(jì)數(shù),來管理自己的引用數(shù)量,當(dāng)計(jì)數(shù)為0時(shí),自動(dòng)釋放內(nèi)存。
什么時(shí)候該用它呢?
本來我有個(gè)A類型的指針:
A *a = new A();
這樣我還要操心去delete它。假如這行代碼在一個(gè)函數(shù)內(nèi),并且a會(huì)作為該函數(shù)的返回值返回的話(比如第一周的作業(yè)Date類隨機(jī)產(chǎn)生10個(gè)日期返回的函數(shù))那選擇什么時(shí)候delete就很重要了。有的時(shí)候不好抉擇,增加維護(hù)的復(fù)雜程度。
那么這就是智能指針大顯身手的時(shí)候。
shared_ptr<A> a = make_shared<A>();
起到了和剛才那條語句一樣的效果。
假如這個(gè)函數(shù)是
shared_ptr<A> fun() {
shared_ptr<A> a = make_shared<A>();
// 對(duì)a做點(diǎn)什么
return a;
}
這樣的話,函數(shù)里定義a,引用計(jì)數(shù)為1,返回a,a的引用計(jì)數(shù)仍為1.而當(dāng)a使用完畢后,到了函數(shù)使用時(shí)所在作用域的結(jié)束位置時(shí)。a的引用計(jì)數(shù)會(huì)-1,此時(shí)為0,自動(dòng)釋放內(nèi)存。不用我們?nèi)斯elete了。
String類不使用shared的模樣
這個(gè)String類是我自己寫的,但跟課程里的只有一點(diǎn)點(diǎn)不一樣。
返回成員的函數(shù)返回類型我設(shè)為const char*,因?yàn)檫@樣的話可以避免使用函數(shù)時(shí)對(duì)成員做出我們不期望的修改。
#ifndef STRING_H
#define STRING_H
#include <cstring>
#include <iostream>
using std::ostream;
class String {
public:
String(const char *cstr = 0);
String(const String&);
String &operator=(const String&);
~String();
const char* get() const;
private:
char *m_data;
};
inline
String::String(const char *cstr) {
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
} else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::String(const String &str) {
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
inline
String& String::operator=(const String &right) {
if (this == &right) return *this;
delete[] m_data;
m_data = new char[strlen(right.m_data) + 1];
strcpy(m_data, right.m_data);
return *this;
}
inline
String::~String() {
delete[] m_data;
}
inline
const char* String::get() const { // 這里返回類型加了const
return m_data;
}
inline
ostream& operator<<(ostream& os, const String& str) {
// auto s = str.get();
// strcpy(s, "h");
return os << str.get();
}
#endif
這是使用后(慘不忍睹,跑不起來):
#ifndef STRING_H
#define STRING_H
#include <cstring>
#include <memory>
#include <iostream>
using std::ostream;
using std::shared_ptr;
using std::make_shared;
class String {
public:
String(const char *cstr = 0);
String(const String&);
String &operator=(const String&);
~String();
const char* get() const;
private:
shared_ptr<char> m_data;
};
inline
String::String(const char *cstr) {
char* pstr;
if (cstr) {
pstr = new char[strlen(cstr) + 1];
strcpy(pstr, cstr);
} else {
pstr = new char[1];
pstr = '\0';
}
m_data = make_shared<char> (pstr, [](char *pstr){ delete[] pstr; });
}
inline
const char* String::get() const {
return m_data.get();
}
inline
ostream& operator<<(ostream& os, const String& str) {
return os << str.get();
}
#endif
這里可以明確的告訴大家,這段程序跑不起啦。但是這里可以看出智能指針的一個(gè)優(yōu)點(diǎn)。那就是使用智能指針后,所有成員都不是指針類型,若沒有特殊操作的話,拷貝構(gòu)造,賦值運(yùn)算符重載,析構(gòu),三大函數(shù)都可以只用默認(rèn)的就可以,極大的減少了代碼量。
但這個(gè)類并不適宜用智能指針。原因現(xiàn)在可能看不太出來。接下來我會(huì)將我對(duì)Date類的改寫和Rectangle類的改寫放出來。這樣就比較清晰了。
Date類同理,也跑不起來,但我還是在這里把代碼貼上
/*
* 用于隨機(jī)生成和排序的數(shù)組
*/
const int DATE_SIZE = 10;
/*
* 生成10個(gè)隨機(jī)日期
*/
unique_ptr<Date[]> CreatePoints() {
unique_ptr<Date[]> dates(new Date[DATE_SIZE]);
for (int i = 0; i < DATE_SIZE; ++i) {
int year = rand() % 1000 + 1500;
int month = rand() % 12 + 1;
int maxDay = 28;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
maxDay = 31;
break;
case 4:
case 6:
case 9:
case 11:
maxDay = 30;
break;
case 2:
if ((year % 4 == 0 && year % 100 != 0)
|| year % 400 == 0)
maxDay = 29;
break;
}
int day = rand() % maxDay + 1;
dates[i] = Date(year, month, day);
}
return dates;
}
/*
* 按從小到大的順序排列日期數(shù)組
*/
unique_ptr<Date[]> Sort(unique_ptr<Date[]> &dates) {
sort(&dates[0], &(dates[DATE_SIZE - 1]) + 1);
return dates;
}
這兩者的共同點(diǎn)就是需要改造成智能指針的都是數(shù)組,也就是說,如果數(shù)組之類對(duì)要綁定成智能指針的變量有依賴或順序關(guān)系的情形下,使用智能指針并不明智,c++ primer書中也講,盡量少用數(shù)組,多用標(biāo)準(zhǔn)庫容器。而不得不用數(shù)組的情況下,還是使用new,delete管理或別的方式比較好。
而有的情形很適合智能指針,比如下面的Rectangle類:
class Shape {
int no;
};
class Point {
int x, y;
public:
Point() = default;
Point(const int &x = 0, const int &y = 0):x(x), y(y){}
};
class Rectangle: public Shape {
int width, height;
shared_ptr<Point> leftUp;
public:
Rectangle(const int&, const int&, const int&, const int&);
// 使用智能指針,拷貝,賦值,析構(gòu)使用默認(rèn)即可
};
inline
Rectangle::Rectangle(const int &w, const int &h, const int &x, const int &y):leftUp(make_shared<Point>(x, y)), width(w), height(h){}
這樣成員只是單個(gè)對(duì)象的時(shí)候?qū)⑵涓臑橹悄苤羔樉秃芩?,Rectangle類的拷貝,賦值,析構(gòu)全部省去,也不用有釋放內(nèi)存問題的擔(dān)心。
這只是我這幾天學(xué)習(xí)c++以來的想法,不一定正確,等以后功力深了說不定我自己都會(huì)推翻現(xiàn)在的想法。所以有什么疑問盡管在下面留言,希望我們的思想能碰撞出智慧的火光,共同進(jìn)步。