C++ 自定义智能指针

C++ Custom Smart Pointer

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

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 未固定为特定(通用)类型。

问题

大部分时间智能指针都在做它的工作...例外情况如下:

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();
}

当 运行 在第一个实例 var1 中使用该代码时,在 test 具有 运行 之后不会被删除。
是的,使用 void* 并不是最好的方法。然而,我们不要偏离主题。代码编译得非常好(如果有人可能质疑我对子赋值运算符的使用)。如果错误是在删除 void* 时,引用计数器 uses 将被删除,但事实并非如此。
我之前检查过析构函数,它们都按应有的方式被调用。
还要注意程序 运行 没有错误。

提前谢谢大家,
谢尔顿

我发现您的代码存在的三大问题是:

  1. 您将分配的对象指针存储为 void*,然后按原样对其调用 delete。那不会调用对象的析构函数。您必须在调用 delete 之前将 void* 类型转换回原始类型,但您不能这样做,因为在 Var 构造函数退出后您丢失了类型信息。

  2. 你已经把对象指针和引用计数器分开了。它们应始终保持在一起。最好的方法是将它们存储在 struct 中,然后根据需要分配和传递它。

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

尝试使用此替代实施方式 (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);
}

Update:也就是说,Var 所做的与早期版本使用 std::any wrapped in a std::shared_ptr, so you may as well just use those instead (std::any is in C++17 and higher only, use 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);
}