C ++自定义智能指针

时间:2017-08-16 22:01:02

标签: c++ pointers smart-pointers

最近我尝试实现自己的智能指针版本。实现看起来有点像:

class Var {
private:
    void* value;
    unsigned short* uses;
public:
    Var() : value(nullptr), uses(new unsigned short(1)) { }
    template<typename K>
    Var(K value) : value((void*)new K(value)), uses(new unsigned short(1)) { }
    Var(const Var &obj) {
        value = obj.value;
        (*(uses = obj.uses))++;
    }
    ~Var() {
        if (value == nullptr && uses == nullptr) return;
        if (((*uses) -= 1) <= 0) {
            delete value;
            delete uses;
            value = uses = nullptr;
        }
    }
    Var& operator=(const Var& obj) {
        if (this != &obj) {
            this->~Var();
            value = obj.value;
            (*(uses = obj.uses))++;
        }
        return *this;
    }
};

实施应该是直截了当的,因为value持有指针并且uses计算引用。
请注意,指针存储为void*,指针类不固定为某些(通用)类型。

问题

大多数情况下,智能指针执行它的工作......例外情况如下:

class C {
public:
    Var var;
    C(Var var) : var(var) {}
};
void test() {
    std::string string = std::string("Heyo");
    Var var1 = Var(string);
    C c = C(var1);
    Var var2 = Var(c);
}
void main() {
    test();
}

运行该代码时,第一个实例var1test运行后不会被删除。
是的,使用void*并不是最好的方法。但是,不要让主题脱离。代码编译完全正常(如果有人质疑我使用sub-assign运算符)。如果错误将删除void*引用计数器uses,则会被删除但不是。
我之前已经检查了析构函数,并且它们都应该被调用 还要注意,程序运行没有错误。

提前谢谢大家,
谢尔顿

1 个答案:

答案 0 :(得分:3)

我在您的代码中看到的三个大问题是:

  1. 您将分配的对象指针存储为void*,然后按原样调用delete。那不会调用对象的析构函数。在调用void*之前,您必须将delete键入回原始类型,但由于在Var构造函数退出后丢失了类型信息,因此无法执行此操作。

  2. 您已将对象指针和引用计数器相互分开。他们应该随时保持在一起。最好的方法是将它们存储在struct中,然后根据需要分配并传递它。

  3. 您的operator=正在调用this->~Var(),这是完全错误的。执行此操作后,this指向的对象不再有效!您需要保持实例处于活动状态,因此只需减少其当前引用计数器,在需要时释放其存储的对象,然后从源Var复制指针并增加该引用计数器。

  4. 请尝试使用此替代实施(Live Demo):

    class Var
    {
    private:
        struct controlBlockBase
        {
            unsigned short uses;    
    
            controlBlockBase() : uses(1) { }
            virtual ~controlBlockBase() { }
        };
    
        template <class K>
        struct controlBlockImpl : controlBlockBase
        {
            K value;
            controlBlockImpl(const K &val) : controlBlockBase(), value(val) {}
        };
    
        controlBlockBase *cb;
    
    public:
        Var() : cb(nullptr) { }
    
        template<typename K>
        Var(const K &value) : cb(new controlBlockImpl<K>(value)) { }
    
        Var(const Var &obj) : cb(obj.cb) {
            if (cb) {
                ++(cb->uses);
            }
        }
    
        Var(Var &&obj) : cb(nullptr) {
            obj.swap(*this);
        }
    
        ~Var() {
            if ((cb) && ((cb->uses -= 1) <= 0)) {
                delete cb;
                cb = nullptr;
            }
        }
    
        Var& operator=(const Var& obj) {
            if (this != &obj) {
                Var(obj).swap(*this);
            }
            return *this;
        }
    
        Var& operator=(Var &&obj) {
            obj.swap(*this);
            return *this;
        }
    
        /* or, the two above operator= codes can be
        merged into a single implementation, where
        the input parameter is passed by non-const
        value and the compiler decides whether to use
        copy or move semantics as needed:
    
        Var& operator=(Var obj) {
            obj.swap(*this);
            return *this;
        }    
        */
    
        void swap(Var &other)
        {
            std::swap(cb, other.cb);
        }
    
        unsigned short getUses() const {
            return (cb) ? cb->uses : 0;
        }
    
        template<class K>
        K* getAs() {
            if (!cb) return nullptr;
            return &(dynamic_cast<controlBlockImpl<K>&>(*cb).value);
        }
    };
    
    void swap(Var &v1, Var v2) {
        v1.swap(v2);
    }
    

    更新:话虽如此,Var所做的与使用std::any中包含的std::shared_ptr的效果基本相同,所以你可以这样做好吧,只需使用它们(std::any仅在C ++ 17及更高版本中,对早期版本使用boost::any):

    class Var
    {
    private:
        std::shared_ptr<std::any> ptr;
    
    public:
        template<typename K>
        Var(const K &value) : ptr(std::make_shared<std::any>(value)) { }
    
        void swap(Var &other) {
            std::swap(ptr, other.ptr);
        }
    
        long getUses() const {
            return ptr.use_count();
        }
    
        template<class K>
        K* getAs() {
            return any_cast<K>(ptr.get());
        }
    };
    
    void swap(Var &v1, Var &v2) {
        v1.swap(v2);
    }