cpp-ipc/include/libipc/mem/container_allocator.h
木头云 8bd5c83349 fix(container_allocator): Fix MSVC error by correcting allocator semantics
ROOT CAUSE:
The allocate() function was incorrectly constructing objects during memory
allocation, violating C++ allocator requirements. MSVC's std::_Tree_node has
a deleted default constructor, causing compilation failure.

CHANGES:
1. container_allocator::allocate() - Now only allocates raw memory without
   constructing objects (removed mem::$new and ipc::construct calls)
2. container_allocator::deallocate() - Now only frees memory without
   destroying objects (removed mem::$delete and ipc::destroy_n calls)

WHY THIS FIXES THE ISSUE:
- C++ allocator semantics require strict separation:
  * allocate()   -> raw memory allocation
  * construct()  -> object construction
  * destroy()    -> object destruction
  * deallocate() -> memory deallocation

- std::map and other containers call construct() with proper arguments
  (key, value) to initialize nodes, not allocate()

- std::_Tree_node in MSVC has no default constructor (= delete), so
  attempting to construct it without arguments always fails

- The previous code tried to default-construct objects in allocate(),
  which is both semantically wrong and impossible for _Tree_node

PREVIOUS FIX (uninitialized.h):
The earlier fix to uninitialized.h was insufficient - even with correct
T() vs T{} handling, you cannot default-construct a type with deleted
default constructor.

Fixes MSVC 2017 compilation error:
error C2280: attempting to reference a deleted function
2025-12-01 07:48:44 +00:00

102 lines
3.1 KiB
C++

/**
* \file libipc/container_allocator.h
* \author mutouyun (orz@orzz.org)
* \brief An allocator that can be used by all standard library containers.
*/
#pragma once
#include <cstddef>
#include <utility>
#include <limits>
#include "libipc/imp/uninitialized.h"
#include "libipc/mem/new.h"
namespace ipc {
namespace mem {
/**
* \brief An allocator that can be used by all standard library containers.
*
* \see https://en.cppreference.com/w/cpp/memory/allocator
* https://en.cppreference.com/w/cpp/memory/polymorphic_allocator
*/
template <typename T>
class container_allocator {
template <typename U>
friend class container_allocator;
public:
// type definitions
typedef T value_type;
typedef value_type * pointer;
typedef const value_type *const_pointer;
typedef value_type & reference;
typedef const value_type &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// the other type of std_allocator
template <typename U>
struct rebind {
using other = container_allocator<U>;
};
container_allocator() noexcept {}
// construct by copying (do nothing)
container_allocator (container_allocator<T> const &) noexcept {}
container_allocator& operator=(container_allocator<T> const &) noexcept { return *this; }
// construct from a related allocator (do nothing)
template <typename U> container_allocator (container_allocator<U> const &) noexcept {}
template <typename U> container_allocator &operator=(container_allocator<U> const &) noexcept { return *this; }
container_allocator (container_allocator &&) noexcept = default;
container_allocator& operator=(container_allocator &&) noexcept = default;
constexpr size_type max_size() const noexcept {
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
}
pointer allocate(size_type count) noexcept {
if (count == 0) return nullptr;
if (count > this->max_size()) return nullptr;
// Allocate raw memory without constructing objects
// Construction should be done by construct() member function
void *p = mem::alloc(sizeof(value_type) * count);
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type count) noexcept {
if (count == 0) return;
if (count > this->max_size()) return;
// Deallocate raw memory without destroying objects
// Destruction should be done by destroy() member function before deallocate
mem::free(p, sizeof(value_type) * count);
}
template <typename... P>
static void construct(pointer p, P && ... params) {
std::ignore = ipc::construct<T>(p, std::forward<P>(params)...);
}
static void destroy(pointer p) {
std::ignore = ipc::destroy(p);
}
};
template <typename T, typename U>
constexpr bool operator==(container_allocator<T> const &, container_allocator<U> const &) noexcept {
return true;
}
template <typename T, typename U>
constexpr bool operator!=(container_allocator<T> const &, container_allocator<U> const &) noexcept {
return false;
}
} // namespace mem
} // namespace ipc