什么樣的類不適宜用智能指針(Boolan)

我最不喜歡循規循矩,雖然是讓寫筆記,照著老師的ppt抄一遍有什么意思。所以我還是喜歡寫自己的東西。

最近我有個怪癖,愛把所有帶指針的類都改造成使用智能指針來控制資源分配和回收。因為我認為既然是c++11標準出的應該是可以頂替99標準,更安全更先進的用法,為什么不用呢?結果在這兩周侯捷老師的c++課上的例子的智能指針改寫上吃了苦頭,也領悟到什么時候該用智能指針,什么時候不該用。離提交作業的日期只剩兩天不到,有空的話我會將我對Date類,Rectangle類的改寫也在這里講一下。

我這里目的并不是講解智能指針,所以講的并不細。

什么是智能指針:

智能指針就是通過模板實現的對普通指針的一種封裝,我偏愛用shared_ptr指針。通過引用計數,來管理自己的引用數量,當計數為0時,自動釋放內存。

什么時候該用它呢?

本來我有個A類型的指針:

A *a = new A();

這樣我還要操心去delete它。假如這行代碼在一個函數內,并且a會作為該函數的返回值返回的話(比如第一周的作業Date類隨機產生10個日期返回的函數)那選擇什么時候delete就很重要了。有的時候不好抉擇,增加維護的復雜程度。

那么這就是智能指針大顯身手的時候。

shared_ptr<A> a = make_shared<A>();

起到了和剛才那條語句一樣的效果。
假如這個函數是

shared_ptr<A> fun() {
  shared_ptr<A> a = make_shared<A>();
// 對a做點什么
 return a;
}

這樣的話,函數里定義a,引用計數為1,返回a,a的引用計數仍為1.而當a使用完畢后,到了函數使用時所在作用域的結束位置時。a的引用計數會-1,此時為0,自動釋放內存。不用我們人工delete了。

String類不使用shared的模樣

這個String類是我自己寫的,但跟課程里的只有一點點不一樣。
返回成員的函數返回類型我設為const char*,因為這樣的話可以避免使用函數時對成員做出我們不期望的修改。


#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

這里可以明確的告訴大家,這段程序跑不起啦。但是這里可以看出智能指針的一個優點。那就是使用智能指針后,所有成員都不是指針類型,若沒有特殊操作的話,拷貝構造,賦值運算符重載,析構,三大函數都可以只用默認的就可以,極大的減少了代碼量。

但這個類并不適宜用智能指針。原因現在可能看不太出來。接下來我會將我對Date類的改寫和Rectangle類的改寫放出來。這樣就比較清晰了。

Date類同理,也跑不起來,但我還是在這里把代碼貼上

/*
 * 用于隨機生成和排序的數組
 */
const int DATE_SIZE = 10;
/*
 * 生成10個隨機日期
 */
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;
}
 
/*
 *    按從小到大的順序排列日期數組
 */
unique_ptr<Date[]> Sort(unique_ptr<Date[]> &dates) {
    sort(&dates[0], &(dates[DATE_SIZE - 1]) + 1);
    return dates;
}

這兩者的共同點就是需要改造成智能指針的都是數組,也就是說,如果數組之類對要綁定成智能指針的變量有依賴或順序關系的情形下,使用智能指針并不明智,c++ primer書中也講,盡量少用數組,多用標準庫容器。而不得不用數組的情況下,還是使用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&);
    // 使用智能指針,拷貝,賦值,析構使用默認即可
};

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){}

這樣成員只是單個對象的時候將其改為智能指針就很爽啦,Rectangle類的拷貝,賦值,析構全部省去,也不用有釋放內存問題的擔心。

這只是我這幾天學習c++以來的想法,不一定正確,等以后功力深了說不定我自己都會推翻現在的想法。所以有什么疑問盡管在下面留言,希望我們的思想能碰撞出智慧的火光,共同進步。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容