我正在努力解决一些遗留代码试图与现代系统对话的问题。具体来说,C++11版本的STL (如果建议的解决方案与C++03一起工作,则加分,如果解决方案只适用于C++17,则为负值,但我仍然感兴趣的是,如果我可以用它作为升级的论据)。
我的函数被传递给一个由void*和三个函数指针组成的数组,这三个函数指针是用于比较、复制和释放每个空指针中的数据的函数(对任何给定的调用,空指针都是相同的数据类型)。总之,我有所有的部分来使这些空*看起来像对象,但它们实际上不是对象。
在我的函数中,我想使用一些用于std::set和std::map的库来处理这些数据(在我们自己的代码库中还有一些其他库,但是set和map是很好的起点)。void*需要被视为值对象--也就是说,当我执行mySet.insert(x)时,它应该分配一个新指针(我有一种方法来查询指针的大小),然后调用copy函数将x的内容复制到集合中。
TL;DR:任何人都知道如何编写使用std::set的自定义分配器,其副本/dealloc指令不包含在副本构造函数?中。
显然,我可以把这四件事组合成一个类:
class BundleStuffTogether {
    BundleStuffTogether(void *data, CompareFunc compare, CopyFunc copy, DeallocFunc dealloc);
    // And create the rest of class accordingly, storing the values, 
    // and with destructor calling dealloc, copy constructor calling
    // copy, etc.
};如果可以的话,我想避免分配那门课。我不希望需要4x大小的集合中每个条目的内存开销(很多指针都指向少量的数据,因此相对大小很大)。
我正在研究如何创造性地使用std::set并填入这两个空白。我可以将比较函数指针作为集合和映射的比较对象传递。这很简单。更难的部分是分配,去分配,和复制。
我一直在尝试编写一个自定义分配器,实际上,当我在测试中直接调用它时,我得到了一个分配器,但是当我将它插入std::set时,事情就崩溃了。我确实创建了一个类,只是为了保持空*,以便能够对分配程序执行模板专门化(下面是详细信息)。
我的第一个窍门是创建这个类:
class Wrapper {
     public: void *_ptr;
};
// which allows for this, given "void *y":
Wrapper *wrap = static_cast<Wrapper*>(&y);这给了我一个可以用于模板专门化的特定类型。使用它,我尝试为包装创建一个成功的std::allocator类型专门化。这对于包装器本身是有效的,但是当我尝试给定制的专门化附加字段来存储函数时,它就崩溃了--分配器必须进行相等的比较。
然后我克隆了std::allocator并创建了我自己的MyAllocator模板类--与std::allocator完全相同的代码--然后为包装创建了一个模板专门化。然后,我给出了主模板和我的专门化操作空白所需的函数,所以现在它们是相等的。
这是一个成功的分配器!我直接用分配器测试了许多变体,而且它起了作用。但是当我把它插入std::set时,它就崩溃了。集合不直接分配我的类。它分配包含我的类的节点..。节点假设我的类有一个副本构造函数。叹息,我认为与std::set的约定是,它将使用" construct“方法在其自己的节点中实际构造包装器对象,但显然并非如此。
所以现在我被困住了。C++17报告说,它甚至反对我所押注的“构造”和“破坏”方法,因此,似乎根本没有办法插入自定义构造函数。
,除了我一开始就有的BundleStuffTogether解决方案之外,还有人能提出其他解决方案吗?我的下一个最佳想法是将std核心化::自我设置并重写其内部,如果我能够避免,我真的不想走这条路。
发布于 2018-04-11 21:35:21
不是的。分配程序概念中没有允许“复制”函数的部分。对STL来说这是不可能的。您唯一真正的希望是分配一个包装类。但是,如果您狡猾,则可以缩小大小,方法是使每个实例都有一个指向伪类型的指针。
struct WrapperType {
  using compare_t = int(void*,void*);
    using copy_t    = void*(void*);
    using free_t    = void(void*);
    compare_t* _compare;
    copy_t*    _copy;
    free_t*    _free;
};
struct Wrapper {
    void*      _data;
    WrapperType* _type;
    explicit Wrapper(void* data, WrapperType* type) noexcept : _data(data), _type(type) {}
    Wrapper(Wrapper&& other) noexcept : _data(other._data), _type(other._type) {}
    Wrapper& operator=(Wrapper&& other) noexcept 
    {reset(); _data=other.release(); _type=other._type; return *this;}
    ~Wrapper() noexcept {reset();}
    void reset() noexcept {_type._free(_data); _data=nullptr;}
    void* release() noexcept {void* data=_data; _data=nullptr; return data;}
    boolean operator<(const Wrapper&other) noexcept {
        assert(_type==other._type);
        return _type._compare(_data, other._data)<0;
    }
};在移动构造函数和移动赋值操作符方面,noexcept在这里非常有用。有了这些,C++库通常会使用它们。否则,C++库通常会倾向于复制版本。
发布于 2018-04-11 21:42:00
首先,就我个人而言,我避免了自定义分配器,因为它们对我来说不太合适。这并不是一个很好的理由,你可能会得到一个基于分配器的答案,但不是从我那里。(参见Alexandrescu在“2015年CppCon:分配器是分配std::向量给烦恼的”中就此事发表的讲话)。
第二,如果您担心内存开销,那么就不应该使用std::set,因为它有大量的内存开销。它也很慢。
但是,即使忽略了这一点,您也可以避免所提到的开销,方法是不为每个set实例保留函数的副本。毕竟,对于所有对象,它们都是相同的;所以您可以执行以下操作之一:
https://stackoverflow.com/questions/49783576
复制相似问题