为什么这个C++ STL分配器没有分配?

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;
}

我期望会打印出 "Yo!",然后由于我实际上没有分配任何内容,会出现一些可怕的错误。但是,程序运行良好并且什么也没有打印。我做错了什么?

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


相关内容,请参见为什么不要继承自std :: allocator - jww
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节。


关于阅读实际接口要求的好处是很重要的——否则你永远不会确定你正在处理分配器应该做的一切。 - j_random_hacker

5

在这种情况下,问题是我没有覆盖分配器的rebind成员。这个版本在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是正确的,不应该用这种方式做。

1
实际上,虚函数分派在分配器调用时并不使用,因为类型在编译时已知,所以我认为这现在应该总是有效的。不继承std :: allocator的唯一原因是它实际上并没有节省您太多打字! - j_random_hacker
但是请参考litb的答案,因为你还需要实现另一个要求(模板复制构造函数)。 - j_random_hacker

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> >

看起来 max_size() 应该返回 std::numeric_limits<size_t>::max() / sizeof(MY_TYPE) - Constantin
@Constantin:非常好的观察,非常感谢!我会相应地更新代码的。 :-) - Stefan Rådström

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++标准。没有禁止从中继承的规定。事实上,据我所知,在标准的任何部分都没有这样的禁令。


你测试过这段代码吗?它不能工作,因为它基本上与问题相同。我也在调试和发布模式下运行了它,但它不起作用。 - Klaim
我使用i686-apple-darwin8-g++-4.0.1没有问题。 - Adam Rosenfield
是的,我已经测试过了 g+++ 3.4.5 - zab。 - anon
1
因此,考虑到这一点以及marvinalone的发现他需要实现rebind<U>,上述代码是否有效似乎取决于vector<T>的实现是否在内部使用rebind。 - j_random_hacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接