c++ - 为什么此分配器不适用于 `std::allocate_shared` ?奇怪的模板替换错误

标签 c++ templates memory-management c++20 allocator

Demo
foo_allocator是 STL 容器的工作分配器。它覆盖了 base分配器类型和转发allocate() , deallocate() , operator== , operator!=等到基地。

#include <iostream>
#include <string>
#include <vector>
#include <memory>

template <typename T>
class bar_allocator : public std::allocator<T> {};

template <typename T, typename base=bar_allocator<T>>
class foo_allocator {
public:
    typedef typename std::allocator_traits<base>::value_type value_type;

    template <typename U, typename A> friend class foo_allocator;

    template<class U>
    struct rebind {
        typedef foo_allocator<U,
                typename std::allocator_traits<base>::template rebind_alloc<U>> other;
    };

    // Construct a dummy allocator from another dummy allocator with the same base_allocator but with different type.
    template <typename U>
    foo_allocator(
            const foo_allocator<U,
                    typename std::allocator_traits<base>::template rebind_alloc<U>>& other) noexcept :
            alloc(other.alloc) {}

    foo_allocator() = default;

    template<typename... Args>
    foo_allocator(Args &&... args) requires (std::is_constructible_v<base, Args...>) : alloc(std::forward<Args>(args)...) {}

    T* allocate(std::size_t n) {
        T* p = alloc.allocate(n);
        return p;
    }

    void deallocate(T* p, std::size_t size) noexcept {
        alloc.deallocate(p, size);
    }

private:
    base alloc;
};

template <typename T, typename U, typename base_allocator>
inline bool operator == (const foo_allocator<T, base_allocator>& a,
        const foo_allocator<U, typename std::allocator_traits<base_allocator>::template rebind_alloc<U>>& b)
{
    return a.alloc == b.alloc;
}

template <typename T, typename U, typename base_allocator>
inline bool operator != (const foo_allocator<T, base_allocator>& a,
        const foo_allocator<U, typename std::allocator_traits<base_allocator>::template rebind_alloc<U>>& b)
{
    return a.alloc != b.alloc;
}


int main()
{
    // Works fine
    std::vector<int, foo_allocator<int>> v;
    for (int i = 0; i < 100; i++)
        v.push_back(i);

    // Breaks!    
    // foo_allocator<int> foo;    
    // std::shared_ptr<int> ptr2 = std::allocate_shared<int>(foo);
}


但是,它不适用于 allocate_shared .如果您尝试 allocate_shared使用此分配器,您会收到以下编译器错误:
In file included from /usr/local/include/c++/9.2.0/bits/shared_ptr.h:52,
                 from /usr/local/include/c++/9.2.0/memory:81,
                 from main.cpp:4:
/usr/local/include/c++/9.2.0/bits/shared_ptr_base.h: In instantiation of 'std::__shared_count<_Lp>::__shared_count(_Tp*&, std::_Sp_alloc_shared_tag<_Alloc>, _Args&& ...) [with _Tp = int; _Alloc = foo_allocator<int>; _Args = {}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]':
/usr/local/include/c++/9.2.0/bits/shared_ptr_base.h:1344:71:   required from 'std::__shared_ptr<_Tp, _Lp>::__shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = foo_allocator<int>; _Args = {}; _Tp = int; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'
/usr/local/include/c++/9.2.0/bits/shared_ptr.h:359:59:   required from 'std::shared_ptr<_Tp>::shared_ptr(std::_Sp_alloc_shared_tag<_Tp>, _Args&& ...) [with _Alloc = foo_allocator<int>; _Args = {}; _Tp = int]'
/usr/local/include/c++/9.2.0/bits/shared_ptr.h:701:14:   required from 'std::shared_ptr<_Tp> std::allocate_shared(const _Alloc&, _Args&& ...) [with _Tp = int; _Alloc = foo_allocator<int>; _Args = {}]'
main.cpp:71:62:   required from here
/usr/local/include/c++/9.2.0/bits/shared_ptr_base.h:676:43: error: no matching function for call to 'foo_allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>, std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> > >::foo_allocator(const foo_allocator<int>&)'
  676 |    typename _Sp_cp_type::__allocator_type __a2(__a._M_a);
      |                                           ^~~~
main.cpp:32:5: note: candidate: 'foo_allocator<T, base>::foo_allocator(Args&& ...) requires  is_constructible_v<base, Args ...> [with Args = {const foo_allocator<int, bar_allocator<int> >&}; T = std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>; base = std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> >]'
   32 |     foo_allocator(Args &&... args) requires (std::is_constructible_v<base, Args...>) : alloc(std::forward<Args>(args)...) {}
      |     ^~~~~~~~~~~~~
main.cpp:32:5: note:   constraints not satisfied
main.cpp:32:5: note: 'is_constructible_v<base, Args ...>' evaluated to false
main.cpp:29:5: note: candidate: 'constexpr foo_allocator<T, base>::foo_allocator() [with T = std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>; base = std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> >]'
   29 |     foo_allocator() = default;
      |     ^~~~~~~~~~~~~
main.cpp:29:5: note:   candidate expects 0 arguments, 1 provided
main.cpp:24:5: note: candidate: 'template<class U> foo_allocator<T, base>::foo_allocator(const foo_allocator<U, typename std::allocator_traits<_Alloc>::rebind_alloc<U> >&)'
   24 |     foo_allocator(
      |     ^~~~~~~~~~~~~
main.cpp:24:5: note:   template argument deduction/substitution failed:
In file included from /usr/local/include/c++/9.2.0/bits/shared_ptr.h:52,
                 from /usr/local/include/c++/9.2.0/memory:81,
                 from main.cpp:4:
/usr/local/include/c++/9.2.0/bits/shared_ptr_base.h:676:43: note:   mismatched types 'std::allocator<_CharT>' and 'bar_allocator<int>'
  676 |    typename _Sp_cp_type::__allocator_type __a2(__a._M_a);
      |                                           ^~~~
main.cpp:10:7: note: candidate: 'constexpr foo_allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>, std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> > >::foo_allocator(const foo_allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>, std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> > >&)'
   10 | class foo_allocator {
      |       ^~~~~~~~~~~~~
main.cpp:10:7: note:   no known conversion for argument 1 from 'const foo_allocator<int>' to 'const foo_allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>, std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> > >&'
main.cpp:10:7: note: candidate: 'constexpr foo_allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>, std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> > >::foo_allocator(foo_allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>, std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> > >&&)'
main.cpp:10:7: note:   no known conversion for argument 1 from 'const foo_allocator<int>' to 'foo_allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>, std::allocator<std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> > >&&'

我可以从错误消息中收集到什么
  • std::shared_ptr做一些重新绑定(bind)以创建一个包裹 int 的结构。我们正在尝试分配。特别是,它尝试分配类型 std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic> ,我猜它有 int还有其他用于确保 shared_ptr 的字段s 是线程安全的。
  • 结果,我们得到了这个非常丑陋的结构,它使用 foo_allocator 进行一些自引用。 :
  • foo_allocator<
        std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>,
        std::allocator<
            std::_Sp_counted_ptr_inplace<int, foo_allocator<int>, __gnu_cxx::_S_atomic>
        > 
    >
    

    可是等等!为什么是 std::allocator<>这里?我们从未指定 std::allocator<>作为我们的基本分配器!应该是 bar_allocator !
  • 后来在错误日志中,我们看到以下奇怪的行:
  • mismatched types 'std::allocator<_CharT>' and 'bar_allocator<int>'
    

    除了 std::allocator<>再次出现, _CharT 去哪儿了来自?这种模板类型通常不显示在字符串中吗?

    任何帮助将不胜感激。我已经为此挠头了一段时间,无法想出任何合理的修复方法。

    最佳答案

    感谢@Nicol Bolas 和@Igor Tandetnik,我能够找出原因。正如他们所说,通过继承分配器,您还继承了实际上为基类重新绑定(bind)的重新绑定(bind)结构。这不是我们想要的(我猜分配器不适用于继承),所以我们必须添加以下内容才能使其工作:

    template <typename T>
    class bar_allocator : public std::allocator<T> {
    public:
        bar_allocator() = default;
    
        template <typename U>
        bar_allocator(const bar_allocator<U>& other) : std::allocator<T>(other){
        }
    
        template<class U>
        struct rebind {
            typedef bar_allocator<U> other;
        };
    };
    

    Demo

    关于c++ - 为什么此分配器不适用于 `std::allocate_shared` ?奇怪的模板替换错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62207280/

    相关文章:

    c++ - C++ 中的多维可变大小数组

    c++ - 强制使通用模板失败,但允许特化,加上 "method exists dispatching"

    c++ - 防止从 unique_ptr 到 shared_ptr 的赋值

    c++ - segmentation 故障的常见原因的明确列表

    c++ - CppCheck自定义规则不显示消息

    c++ - 如果大于则无锁增量

    c++ - C++ 模板中的类型名

    c 内存子分配器,在结构、链表和指针转换方面遇到问题

    java - JVM 堆分配

    c - 字符串存储在内存的哪一部分?