最近我尝试实现自己的智能指针版本。实现看起来有点像下面:
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();
}
运行该代码时,第一个实例var1
在运行后不会删除test
.
是的,使用void*
并不是最好的方法。然而,我们不要跑题。代码编译得很好(如果有人可能会质疑我对子赋值运算符的使用(。如果错误是在删除void*
引用计数器时,uses
将被删除,但事实并非如此.
我之前已经与析构函数进行了检查,它们都被调用了。
另请注意,程序运行没有错误。
提前谢谢大家,
谢尔顿
我看到你的代码的三个大问题是:
-
您将分配的对象指针存储为
void*
,然后按原样调用delete
。 这不会调用对象的析构函数。 在调用delete
之前,您必须将void*
类型转换回原始类型,但您不能这样做,因为您在Var
构造函数退出后丢失了类型信息。 -
您已将对象指针和引用计数器彼此分开。 它们应始终保持在一起。 最好的方法是将它们存储在一个
struct
中,然后根据需要分配和传递。 -
你的
operator=
正在打电话给this->~Var()
,这是完全错误的。 一旦你这样做,this
指向的对象就不再有效! 您需要使实例保持活动状态,因此只需递减其当前引用计数器,根据需要释放其存储的对象,然后从源Var
复制指针并递增该引用计数器。
请尝试以下替代实现(实时演示(:
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::shared_ptr
中的std::any
的效果基本相同,因此您不妨改用它们(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);
}