什么样的类不适宜用智能指针(Boolan)

作者: T4Technology | 来源:发表于2017-07-26 20:37 被阅读343次

    我最不喜欢循规循矩,虽然是让写笔记,照着老师的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++以来的想法,不一定正确,等以后功力深了说不定我自己都会推翻现在的想法。所以有什么疑问尽管在下面留言,希望我们的思想能碰撞出智慧的火光,共同进步。

    相关文章

      网友评论

        本文标题:什么样的类不适宜用智能指针(Boolan)

        本文链接:https://www.haomeiwen.com/subject/hvynkxtx.html