12

我正在尝试编写一个派生自的自定义 STL 分配器std::allocator,但不知何故所有调用都allocate()转到基类。我已将其缩小到以下代码:

template <typename T> class a : public std::allocator<T> {
public:
    T* allocate(size_t n, const void* hint = 0) const {
        cout << "yo!";
        return 0;
    }
};

int main()
{
    vector<int, a<int>> v(1000, 42);
    return 0;
}

我期待“哟!” 打印出来,然后是一些可怕的错误,因为我实际上并没有分配任何东西。相反,该程序运行良好并且什么也不打印。我究竟做错了什么?

我在 gcc 和 VS2008 中得到相同的结果。

4

4 回答 4

7

您将需要提供重新绑定成员模板和 C++ 标准的分配器要求中列出的其他内容。例如,您需要一个模板复制构造函数,它不仅接受allocator<T>而且还接受allocator<U>. 例如,一个代码可能会这样做,例如 std::list 可能会这样做

template<typename Allocator>
void alloc1chunk(Allocator const& alloc) {
    typename Allocator::template rebind<
        wrapper<typename Allocator::value_type>
      >::other ot(alloc);
    // ...
}

如果不存在正确的重新绑定模板或不存在相应的复制构造函数,则代码将失败。猜测需求是什么,您将毫无用处。迟早您将不得不处理依赖于这些分配器要求的一部分的代码,并且代码将失败,因为您的分配器违反了它们。我建议您在您的标准副本中的一些工作草案中查看它们20.1.5

于 2009-02-12T03:01:20.213 回答
5

在这种情况下,问题在于我没有覆盖分配器的重新绑定成员。此版本有效(在 VS2008 中):

template <typename T> class a : public std::allocator<T> {
public:
    T* allocate(size_t n, const void* hint = 0) const {
        cout << "yo!";
        return 0;
    }

    template <typename U> struct rebind
    {
        typedef a<U> other;
    };
};

int main() {
    vector<int, a<int>> v(1000, 42);
    return 0;
}

我通过 STL 标头调试发现了这一点。

这是否有效将完全取决于 STL 的实现,所以我认为最终,Klaim 是正确的,不应该以这种方式完成。

于 2009-02-12T02:12:29.283 回答
2

我有两个用于创建自定义分配器的模板;如果在自定义类型上使用,第一个会自动工作:

template<>
class std::allocator<MY_TYPE>
{
public:
    typedef size_t      size_type;
    typedef ptrdiff_t   difference_type;
    typedef MY_TYPE*    pointer;
    typedef const MY_TYPE*  const_pointer;
    typedef MY_TYPE&    reference;
    typedef const MY_TYPE&  const_reference;
    typedef MY_TYPE     value_type;

    template <class U>
    struct rebind
    {
        typedef std::allocator<U> other;
    };

    pointer allocate(size_type n, std::allocator<void>::const_pointer hint = 0)
    {
        return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T)));
    }
    void construct(pointer p, const_reference val)
    {
        ::new(p) T(val);
    }
    void destroy(pointer p)
    {
        p->~T();
    }
    void deallocate(pointer p, size_type n)
    {
        FREE_FUNC(p);
    }
    size_type max_size() const throw()
    {
        // return ~size_type(0); -- Error, fixed according to Constantin's comment
        return std::numeric_limits<size_t>::max()/sizeof(MY_TYPE);
    }
};

第二个用于当我们想要使用标准分配器为预定义类型使用自己的分配器时,例如 char、wchar_t、std::string 等:

    namespace MY_NAMESPACE
    {

    template <class T> class allocator;

    // specialize for void:
    template <>
    class allocator<void>
    {
    public:
        typedef void*       pointer;
        typedef const void* const_pointer;
        // reference to void members are impossible.
        typedef void        value_type;

        template <class U>
        struct rebind
        {
            typedef allocator<U> other;
        };
    };

    template <class T>
    class allocator
    {
    public:
        typedef size_t      size_type;
        typedef ptrdiff_t   difference_type;
        typedef T*      pointer;
        typedef const T*    const_pointer;
        typedef T&      reference;
        typedef const T&    const_reference;
        typedef T       value_type;

        template <class U>
        struct rebind
        {
            typedef allocator<U> other;
        };

        allocator() throw()
        {
        }
        template <class U>
        allocator(const allocator<U>& u) throw()
        {
        }
        ~allocator() throw()
        {
        }

        pointer address(reference r) const
        {
            return &r;
        }
        const_pointer address(const_reference r) const
        {
            return &r;
        }
        size_type max_size() const throw()
        {
            // return ~size_type(0); -- Error, fixed according to Constantin's comment
            return std::numeric_limits<size_t>::max()/sizeof(T);
        }
        pointer allocate(size_type n, allocator<void>::const_pointer hint = 0)
        {
            return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T)));
        }
        void deallocate(pointer p, size_type n)
        {
            FREE_FUNC(p);
        }

        void construct(pointer p, const_reference val)
        {
            ::new(p) T(val);
        }
        void destroy(pointer p)
        {
            p->~T();
        }
    };

template <class T1, class T2>
inline
bool operator==(const allocator<T1>& a1, const allocator<T2>& a2) throw()
{
    return true;
}

template <class T1, class T2>
inline
bool operator!=(const allocator<T1>& a1, const allocator<T2>& a2) throw()
{
    return false;
}

}

上面的第一个模板,对于您自己定义的类型,不需要任何进一步的处理,而是由标准容器类自动使用。第二个模板在用于标准类型时需要进一步的工作。例如,对于 std::string,在声明该类型的变量时必须使用以下构造(使用 typedef 最简单):

std::basic_string<char>, std::char_traits<char>, MY_NAMESPACE::allocator<char> >
于 2009-02-12T17:07:45.437 回答
1

以下代码按预期打印“yo” - 您看到的是我们的老朋友“未定义行为”。

#include <iostream>
#include <vector>
using namespace std;

template <typename T> class a : public std::allocator<T> {
public:
    T* allocate(size_t n, const void* hint = 0) const {
        cout << "yo!";
        return new T[10000];
    }
};

int main()
{
    vector<int, a<int> > v(1000, 42);
    return 0;
}

编辑:我刚刚检查了有关默认分配器的 C++ 标准。没有禁止继承它。事实上,据我所知,该标准的任何部分都没有这样的禁令。

于 2009-02-12T01:35:37.017 回答