c++ - 创建密集的动态数组(值数组)作为库

标签 c++ data-structures

我尝试创建打包数组作为游戏引擎的数据结构,如下所述:- http://experilous.com/1/blog/post/dense-dynamic-arrays-with-stable-handles-part-1
diagram

简而言之,结构存储的是值,而不是指针。

这是草稿。

template<class T> class Id{
    int id;
}
template<class T> class PackArray{
    std::vector <int>indirection ;  //promote indirection here
    std::vector <T>data;
    //... some fields for pooling (for recycling instance of T)
    Id<T> create(){
        data.push_back(T());
        //.... update indirection ...
        return Id( .... index  , usually = indirection.size()-1 .... )
    }
    T* get(Id<T> id){   
        return &data[indirection[id.id]];
        //the return result is not stable, caller can't hold it very long
    }
    //... others function e.g. destroy(Id<T>) ...
}

原型(prototype)如我所愿,但现在我关心的是旧代码的美感。

例如,我总是像这样创建一个新对象:-

Bullet* bullet = new Bullet(gameEngine,velocity);

现在我必须打电话:-

Id<Bullet> bullet = getManager()->create()->ini(velocity);
// getManager() usually return PackArray<Bullet>*
// For this data structure, 
//    if I want to hold the object for a long time, I have to cache it as Id.

以下是问题:-

  1. 新版代码更丑了。
    我应该避免它吗?如何避免?

  2. 如何避免/减少程序员对上述修改的工作量?
    这是非常乏味的,因为它们散落在周围。

(编辑)最可怕的部分是类型声明的变化,例如

class Rocket{
    std::vector<Bullet*> bullets;  
    //-> std::vector<Id<Bullet>> bullets;
    void somefunction(){
       Bullet* bullet = someQuery();
       //-> Id<Bullet> bullet
    }
}//These changes scatter around many places in many files.

此更改(插入单词“Id<>”)意味着游戏逻辑必须知道用于存储 Bullet 的底层数据结构。

如果以后底层的数据结构又要变了,我又得手动一个一个重构(从Id<>改成别的),可维护性比较差。

  1. (可选)这个数据结构/技术的名称是什么?

  2. 作为一个库,Id 是否应该有一个 PackArray* 字段以允许在没有 manager() 的情况下访问底层对象(例如 Bullet*)?

    Bullet* bullet = someId->getUnderlyingObject();

最佳答案

id 的这种行为听起来像 handles ,因为您不提供有关存储方法的信息,但只要句柄有效就保证访问。在后面的方面,句柄的行为类似于原始指针:您将无法判断它是否有效(至少没有管理器)并且句柄可能在某些时候被重用。

如果从原始指针更改为句柄会产生更丑陋的代码,这个问题是非常自以为是的,我宁愿保持这个目标:在可读性明确和过多输入之间存在平衡 - 每个人都在这里画出自己的限制。让调用站点指定 getManager 也有好处:也许这些管理器有多个可能的实例,也许获取管理器需要锁定并且对于多个操作你只想锁定一次。 (除了我在下面介绍的内容之外,您还可以支持这两种情况。)

让我们使用指针/迭代器表示法通过我们的句柄访问对象,减少必要的代码更改量。使用 std::make_uniquestd::make_shared作为引用,让我们定义 make_handle将创作分派(dispatch)给合适的经理。我调整了PackArray::create稍微使以下示例更紧凑:

template<class T> class Handle;
template<class T> class PackArray;
template<class T, class... Args> Handle<T> make_handle(Args&&... args);

template<class T>
struct details {
    friend class Handle<T>;
    template<class U, class... Args> friend Handle<U> make_handle(Args&&... args);
private:
    // tight control over who get's to access the underlying storage
    static PackArray<T>& getManager();
};

template<class T>
class Handle {
    friend class PackArray<T>;
    size_t id;

public:
    // accessors (via the manager)
    T& operator*();
    T* operator->() { return &*(*this); }
};

template<class T>
class PackArray {
    std::vector<size_t> idx;
    std::vector<T> data;

public:
    template<class... Args>
    Handle<T> create(Args&&... args) {
        Handle<T> handle;
        handle.id = data.size();
        idx.push_back(data.size());
        // enables non-default constructable types
        data.emplace_back(std::forward<Args>(args)...);
        return handle;
    }
    // access using the handle
    T& get(Handle<T> handle) {
        return data[idx[handle.id]];
    }
};

template<class T, class... Args>
Handle<T> make_handle(Args&&... args) {
    Handle<T> handle = details<T>::getManager().create(std::forward<Args>(args)...);
    return handle;
}

template<class T>
T& Handle<T>::operator*() {
    return details<T>::getManager().get(*this);
}

使用代码如下:

Handle<int> hIntA = make_handle<int>();
Handle<int> hIntB = make_handle<int>(13);
Handle<float> hFloatA = make_handle<float>(13.37f);
Handle<Bullet> hBulletA = make_handle<Bullet>();
// Accesses through the respective managers
*hIntA = 42; // assignment
std::cout << *hIntB; // prints 13
float foo = (*hFloatA + 12.26f) * 0.01;
applyDamage(hBulletA->GetDmgValue());

每种类型都需要一个管理器,也就是说,如果你没有定义一个默认值,你会得到一个编译器错误。或者,您可以提供一个通用实现(注意:instance 的初始化不是线程安全的!):

template<class T>
PackArray<T>& details<T>::getManager() {
    static PackArray<T> instance;
    return instance;
}

您可以通过模板特化获得特殊行为。您甚至可以通过模板特化替换管理器类型,让您轻松比较存储策略(例如 SOA 与 AOS)。

template<>
struct details<Bullet> {
    friend class Handle<Bullet>;
    template<class U, class... Args> friend Handle<U> make_handle(Args&&... args);
private:
    static MyBulletManager& getManager() {
        static MyBulletManager instance;
        std::cout << "special bullet store" << std::endl;
        return instance;
    }
};

您甚至可以使所有这些常量正确(与实现自定义迭代器相同的技术适用)。

您甚至可能想要扩展 details<T>到一个完整的特征类型......这是泛化和复杂性之间的平衡。

关于c++ - 创建密集的动态数组(值数组)作为库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37605056/

相关文章:

delphi - DataSetProvider - DataSet 到 ClientDataSet

performance - 手指树(Data.Sequence)与绳索(Data.Rope)(Haskell,或一般情况下)

c++ - 什么时候允许对 c++11 中的类型进行 memcpyed?

c# - B-Trees/B+Trees 和重复键

c++ - 如何非常快速地找到 2 个几乎相同的文件之间的差异?

c++ - 链接错误 : undefined reference to `vtable for XXX`

c - 关于数据结构的建议

c++ - C++:如何从k = 2 ^ a * b中找到a和b?

c++ - 如何检查 LIB 导入库是否完全匹配其 DLL?

c++ - std::string 到 LPBYTE 和 RegEnumValueA