自定义C++ Allocator无法支持swap?shrink_to_fit报错解决
问题描述
学习C++分配器时,调用std::vector::shrink_to_fit()方法在GCC编译环境下报错,提示std::allocator_traits<MemoryManagerAllocator<int>>::propagate_on_container_swap不存在value成员,但相同代码在MSVC中可正常运行。需要找出自定义分配器缺失的部分并解决该错误。
自定义Allocator代码
#include <cinttypes> #include <iostream> #include <algorithm> #include <vector> #include <list> #include <forward_list> #include <array> #include <map> enum class MemoryBank : std::uint8_t { General, Audio, Video }; template<std::size_t ChunkSize = 1024> class ChunkMemoryManager { public: ChunkMemoryManager() { std::cout << "ChunkMemoryManager()\n"; } char* allocate(std::size_t bytes) { return nullptr; /*skipped*/ } void deallocate(char* address, size_t size) { /*skipped*/ } private: // types struct Chunk { Chunk() { freeSlots.emplace_front(0, ChunkSize); } std::array<char, ChunkSize> memory; std::forward_list<std::pair<std::size_t, std::size_t>> freeSlots; }; private: // data std::list<Chunk> chunks; }; template<typename T, MemoryBank bank = MemoryBank::General, typename MemoryManager = ChunkMemoryManager<1024>> class MemoryManagerAllocator { public: using value_type = T; using propagate_on_container_swap = std::true_type(); template<typename U> struct rebind { using other = MemoryManagerAllocator<U, bank, MemoryManager>; }; MemoryManagerAllocator() = default; MemoryManagerAllocator(const MemoryManagerAllocator&) = default; MemoryManagerAllocator& operator=(const MemoryManagerAllocator& rhs) = default; template<class U, MemoryBank UMemoryBank, typename UMemoryManager> constexpr MemoryManagerAllocator(const MemoryManagerAllocator<U, UMemoryBank, UMemoryManager>&) noexcept {}; [[nodiscard]] T* allocate(std::size_t n) { return reinterpret_cast<T*>(memoryManager.allocate(n * sizeof(T))); } void deallocate(T* p, std::size_t n) noexcept { memoryManager.deallocate(reinterpret_cast<char*>(p), n * sizeof(T)); } private: inline static MemoryManager memoryManager; }; template<class T, MemoryBank TMemoryBank, class TMemoryManager, class U, MemoryBank UMemoryBank, class UMemoryManager> bool operator==(const MemoryManagerAllocator<T, TMemoryBank, TMemoryManager>&, const MemoryManagerAllocator<U, UMemoryBank, UMemoryManager>&) { return true; } template<class T, MemoryBank TMemoryBank, class TMemoryManager, class U, MemoryBank UMemoryBank, class UMemoryManager> bool operator!=(const MemoryManagerAllocator<T, TMemoryBank, TMemoryManager>&, const MemoryManagerAllocator<U, UMemoryBank, UMemoryManager>&) { return false; }
驱动代码
int main() { using MMAllocator = MemoryManagerAllocator<int>; std::vector<int, MMAllocator> v; v.shrink_to_fit(); }
编译错误信息
In file included from /usr/include/c++/13/cstdint:38, from /usr/include/c++/13/cinttypes:38, from /home/user/.vs/allocator/src/main.cpp:1: /usr/include/c++/13/bits/stl_vector.h: In instantiation of ‘constexpr void std::vector<_Tp, _Alloc>::swap(std::vector<_Tp, _Alloc>&) [with _Tp = int; _Alloc = MemoryManagerAllocator<int>]’: /usr/include/c++/13/bits/alloc_traits.h:908:28: required from ‘static constexpr bool std::__shrink_to_fit_aux<_Tp, true>::_S_do_it(_Tp&) [with _Tp = std::vector<int, MemoryManagerAllocator<int> >]’ /usr/include/c++/13/bits/vector.tcc:721:56: required from ‘constexpr bool std::vector<_Tp, _Alloc>::_M_shrink_to_fit() [with _Tp = int; _Alloc = MemoryManagerAllocator<int>]’ /usr/include/c++/13/bits/stl_vector.h:1069:9: required from ‘constexpr void std::vector<_Tp, _Alloc>::shrink_to_fit() [with _Tp = int; _Alloc = MemoryManagerAllocator<int>]’ /home/user/.vs/allocator/src/main.cpp:143:20: required from here /usr/include/c++/13/bits/stl_vector.h:1589:9: error: ‘value’ is not a member of ‘std::allocator_traits<MemoryManagerAllocator<int> >::propagate_on_container_swap’ {aka ‘std::integral_constant<bool, true>()’} 1589 | __glibcxx_assert(_Alloc_traits::propagate_on_container_swap::value | ^~~~~~~~~~~~~~~~ gmake[3]: *** [CMakeFiles/allocator.dir/build.make:76: CMakeFiles/allocator.dir/src/main.cpp.o] Error 1
问题原因与解决方案
错误根源
分配器中propagate_on_container_swap的定义错误:你写成了using propagate_on_container_swap = std::true_type();,这是创建了一个临时对象,而非定义类型别名。而C++标准要求,分配器的propagate_on_container_swap必须是一个类型(通常是std::true_type或std::false_type),allocator_traits会访问该类型的静态成员value来判断是否需要在容器swap时传播分配器。
MSVC对该错误兼容性更宽松,允许这种语法,但GCC严格遵循标准,因此报错。
修正方案
将propagate_on_container_swap的定义改为正确的类型别名,去掉括号:
template<typename T, MemoryBank bank = MemoryBank::General, typename MemoryManager = ChunkMemoryManager<1024>> class MemoryManagerAllocator { public: using value_type = T; // 修正:去掉括号,定义为类型别名 using propagate_on_container_swap = std::true_type; // 其余代码保持不变... };
补充说明
如果你的分配器不需要在容器swap时传播(即交换容器时不需要交换分配器实例),可以将其设为std::false_type。但由于你的分配器使用inline static的memoryManager(共享状态),设置为std::true_type是合理的,确保容器swap时分配器状态同步。
内容的提问来源于stack exchange,提问作者qloq

