【问题标题】:C++ custom allocator size argument as template parameter throws compiler errorC++ 自定义分配器大小参数作为模板参数引发编译器错误
【发布时间】:2020-04-23 03:08:31
【问题描述】:

当我为容器使用自定义分配器时,以下代码给出了预期的结果(将大小 sz 保持为全局变量)

#include <cstddef> /* size_t */
#include <new> /* Only for bad_alloc */
#include <vector>
using std::vector;
#include <iostream>
using std::cout;
using std::endl;
std::size_t constexpr sz = 4;
template <typename T> class StaticAllocator {
protected:
    static T buf[sz];
public:
    typedef T value_type;
    T* allocate(std::size_t const n) {
        if (n > sz) throw std::bad_alloc();
        return buf;
    }
    void deallocate(T*, std::size_t) {}
};
template<typename T> T StaticAllocator<T>::buf[sz];
int main(void) {
    std::vector<char, StaticAllocator<char> > v;
    v.push_back('a');
    v.push_back('b');
    for (auto const& i : v) cout << i << endl;
}

当我尝试将大小用作类的模板参数时,这个版本的代码给了我编译器错误

#include <cstddef> /* size_t */
#include <new> /* bad_alloc */
#include <vector>
using std::vector;
#include <iostream>
using std::cout;
using std::endl;
template<typename T, std::size_t sz> class StaticAllocator {
protected:
    static T buf[sz];
public:
    typedef T value_type;
    T* allocate(std::size_t const n) {
        if (n > sz) throw std::bad_alloc();
        return buf;
    }
    void deallocate(T*, std::size_t) {}
};
template<typename T, std::size_t sz> T StaticAllocator<T, sz>::buf[sz];
int main(void) {
    std::vector<char, StaticAllocator<char, 4> > v;
    v.push_back('a');
    v.push_back('b');
    for (auto const& i : v) cout << i << endl;
}

【问题讨论】:

    标签: c++


    【解决方案1】:

    要从T 类型的分配器中获取某个类型U 的分配器,使用成员别名模板std::allocator_traits::rebind_alloc&lt;U&gt; [allocator.traits.types]:

    Alloc::rebind&lt;T&gt;::other 如果Alloc::rebind&lt;T&gt;::other 有效并且 表示类型;否则,Alloc&lt;T, Args&gt; 如果AllocAlloc&lt;U, Args&gt; 形式的类模板实例化,其中Args 是零个或多个类型参数;否则,rebind_alloc 的实例化格式不正确。

    请注意,Argstype 模板参数。在您的分配器中没有rebind。在第一种情况下使用Alloc&lt;U&gt;Args 为空。但是在第二种情况下,第二个模板参数是一个非类型,它不能被Args匹配。

    需要手动添加rebind成员结构:

    template<typename T, std::size_t sz>
    class StaticAllocator {
        // ...
    
        template<class U>
        struct rebind {
            using other = StaticAllocator<U, sz>;
        };
    };
    

    另请注意,您的分配器对于某些通用类型S 已损坏。首先,buf会默认初始化构造sz对象S。然后,在销毁现有的之前,它将通过在同一位置新建Ss 来覆盖。重新分配时也会发生类似的事情。这可能导致未定义的行为。有关详细信息,请参阅 thisthis 问题。


    现在已删除的答案中提出了以下解决方案:从std::allocator&lt;T&gt;继承:

    template<typename T, std::size_t sz> class StaticAllocator : 
        public std::allocator<T> {
    // ...
    };
    

    代码编译并运行,但是...StaticAllocator::allocateStaticAllocator::deallocate调用(至少在 libstdc++ 中)。原因是内部std::vector总是uses rebind来获取分配器类型:

    using Tp_alloc_type = 
        typename gnu_cxx::alloc_traits<Alloc>::template rebind<Tp>::other;
    

    rebind 继承自 std::allocator&lt;T&gt;,它返回 std::allocator 而不是 StaticAllocator。这就是为什么你仍然需要在StaticAllocator 中提供自己的rebind

    【讨论】:

    • 根据你的建议,我修改了我的类定义,它就像一个魅力。谢谢你。 template&lt;typename T, std::size_t sz&gt; class StaticAllocator { protected: static T buf[sz]; public: typedef T value_type; template&lt;typename U&gt; struct rebind { typedef StaticAllocator&lt;U, sz&gt; other; }; T* allocate(std::size_t const n) { if (n &gt; sz) throw std::bad_alloc(); return buf; } void deallocate(T *, std::size_t) {} };
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    • 2020-10-13
    • 2011-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多