由支持多态性的值池存储,如何使用智能指针

Stored-by-Value Pool that support polymorphism, how to use smart pointer?

本文关键字:何使用 智能 指针 存储 支持 多态性      更新时间:2023-10-16

简介

我有一个数据结构:价值池。(不是指针池)

当我调用create()时,它将返回Handle

到目前为止一切都很好。

template<class T> class Pool{
std::vector<T> v;                   //store by value
Handle<T> create(){  .... }
}
template<class T> class Handle{
Pool<T>* pool_;                    //pointer back to container
int pool_index_;                   //where I am in the container
T* operator->() {                  
return pool_->v.at(pool_index_);     //i.e. "pool[index]"
}
void destroy(){
pool_-> ... destroy(this)  .... mark "pool_index_" as unused, etc ....
}
}

现在我想要句柄<>以支持多态性。

问题

许多专家好心地建议我使用weak_ptr,但我已经一周没用了,不知道该怎么做。

我卡住的主要部分是:-

  • 是否应该create()返回weak_ptr,而不是Handle?……还是应该Handle封装weak_ptr

  • 如果create()为用户的程序返回weak_ptr。。。weak_ptr怎么会知道pool_index_?它没有这样的领域。

  • 如果用户将weak_ptr/Handle强制转换为父类指针,则会出现许多问题:-

例如

class B{}
class C : public B { ......
}
....
{
Pool<C> cs;
Handle<C> cPtr=cs.create();
Handle<B> bPtr=cPtr;     // casting ;expected to be valid, 
//       ... but how? (weak_ptr may solve it)
bPtr->destroy()   ;      // aPtr will invoke Pool<B>::destroy  which is wrong!
//     Pool<C>::destroy is the correct one
bPtr.operator->() ;      // face the same problem as above
}

假设

  • 池总是在Handle之后删除(为了简单起见)
  • 无多线程

这里有类似的问题,但都不够接近
C++对象池,提供删除时返回池的智能指针项
C++11内存池设计模式?

关于weak_ptr

CCD_ 2总是与CCD_ 3相关联。要使用weak_ptr,您必须使用shared_ptr管理对象。这意味着可以共享对象的所有权:任何人都可以从weak_ptr构造shared_ptr并将其存储在某个地方。只有当所有shared_ptr都被销毁时,指向的对象才会被删除。Pool将失去对对象释放的直接控制,因此无法支持公共destroy()函数
有了共享所有权,事情可能会变得非常混乱。

这就是为什么std::unique_ptr通常是对象生命周期管理的更好替代方案的原因之一(遗憾的是,它不适用于weak_ptr)。Handle::destroy()函数还意味着这不是您想要的,Pool应该单独处理其对象的生存期。

然而,shared_ptr/weak_ptr是为多线程应用程序设计的。在单线程环境中,您可以在根本不使用weak_ptr的情况下获得类似weak_ptr的功能(检查有效目标并避免悬挂指针):

template<class T> class Pool {
bool isAlive(int index) const { ... }
}
template<class T> class Handle {
explicit operator bool() const { return pool_->isAlive(pool_index_); }
}

为什么这只能在单线程环境中工作

多线程程序中考虑此场景:

void doSomething(std::weak_ptr<Obj> weak) {
std::shared_ptr<Obj> shared = weak.lock();
if(shared) {
// Another thread might destroy the object right here
// if we didn't have our own shared_ptr<Obj> 
shared->doIt(); // And this would crash
}
}

在上面的情况下,我们必须确保指向的对象在if()之后仍然可以访问。因此,我们构建了一个std::weak_ptr0,无论发生什么,它都能保持生命。

单线程程序中,您不必担心:

void doSomething(Handle<Obj> handle) {
if(handle) {
// No other threads can interfere
handle->doIt();
}
}

在多次取消引用句柄时,仍然需要小心。示例:

void doDamage(Handle<GameUnit> source, Handle<GameUnit> target) {    
if(source && target) {
source->invokeAction(target);
// What if 'target' reflects some damage back and kills 'source'?
source->payMana(); // Segfault
}
}

但是使用另一个if(source),您现在可以轻松地检查句柄是否仍然有效!

铸造手柄

因此,Handle<T>中的模板参数T不一定与池的类型匹配。也许你可以用模板魔法来解决这个问题。我只能想出一个使用动态调度(虚拟方法调用)的解决方案:

struct PoolBase {
virtual void destroy(int index)       = 0;
virtual void* get(int index)          = 0;
virtual bool isAlive(int index) const = 0;
};
template<class T> struct Pool : public PoolBase {
Handle<T> create() { return Handle<T>(this, nextIndex); }
void destroy(int index) override { ... }
void* get(int index) override { ... }
bool isAlive(int index) const override { ... }
};
template<class T> struct Handle {
PoolBase* pool_;
int pool_index_;
Handle(PoolBase* pool, int index) : pool_(pool), pool_index_(index) {}
// Conversion Constructor
template<class D> Handle(const Handle<D>& orig) {
T* Cannot_cast_Handle = (D*)nullptr;
(void)Cannot_cast_Handle;
pool_ = orig.pool_;
pool_index_ = orig.pool_index_;
}
explicit operator bool() const { return pool_->isAlive(pool_index_); }
T* operator->() { return static_cast<T*>( pool_->get(pool_index_) ); }
void destroy() { pool_->destroy(pool_index_); }
};

用法:

Pool<Impl> pool;
Handle<Impl> impl = pool.create();
// Conversions
Handle<Base> base  = impl; // Works
Handle<Impl> impl2 = base; // Compile error - which is expected

检查有效转换的行可能会被优化掉。检查仍将在编译时进行!尝试一个无效的转换会给你这样的错误:

错误:从"Base*"到"Impl*"的转换无效[-fpermission]
T*Cannot_cast_Handle=(D*)nullptr;

我在这里上传了一个简单的、可编译的测试用例:http://ideone.com/xeEdj5