为什么我的代码不适用于自定义分配器?
Why my code doesn't work with custom allocator?
问题是什么以及如何解决?
在不尝试压缩自定义分配器的情况下,我的向量乍一看可以正常工作。
我也很乐意指出我的代码中的任何错误。或者自定义向量或另一个容器的正确实现示例,这将对我有所帮助。
此代码无效:
using MyLib::Vector;
int main()
{
//Vector<int> v; //Works fine
Vector<int> v(CustomAllocator<int>());
for (size_t i = 0; i < 256; i++) {
v.push_back(i); //Error: "expression must have class type"
}
}
CustomAllocator 实现(应该没问题):
template <typename T>
class CustomAllocator : public std::allocator<T>
{
private:
using Base = std::allocator<T>;
public:
T* allocate(size_t count){
std::cout << ">> Allocating " << count << " elements" << std::endl;
return Base::allocate(count);
}
T* allocate(size_t count, const void* p)
{
std::cout << ">> Allocating " << count << " elements" << std::endl;
return Base::allocate(count, p);
}
void deallocate(T* p, size_t count)
{
if (p != nullptr)
{
std::cout << ">> Deallocating " << count << " elements" << std::endl;
Base::deallocate(p, count);
}
}
};
向量实现:
namespace MyLib
{
template <typename T,
template <typename Y> class Allocator = std::allocator>
class Vector
{
private:
std::size_t capacityV;
std::size_t sizeV;
Allocator<T> alloc;
T* arr;
public:
typedef Allocator<T> AllocatorType;
typedef Vector<T, Allocator> VectorType;
using AllocTraits = std::allocator_traits<Allocator<T>>;
public:
explicit Vector(const AllocatorType& allocator = AllocatorType()) {
capacityV = 0;
sizeV = 0;
alloc = allocator;
arr = nullptr;
}
Vector(const std::initializer_list<T>& values,
const AllocatorType& allocator = AllocatorType()) {
sizeV = values.size();
alloc = allocator;
if (sizeV < 128)
capacityV = 128;
else
capacityV = (sizeV / 128) * 256; //that makes sense
arr = AllocTraits::allocate(alloc, capacityV);
AllocTraits::construct(alloc, arr, capacityV);
std::copy(values.begin(), values.end(), arr);
}
~Vector() {
if (arr)
AllocTraits::deallocate(alloc, arr, capacityV);
}
Vector(const Vector& rhs) {
capacityV = rhs.capacityV;
sizeV = rhs.sizeV;
arr = AllocTraits::allocate(alloc, capacityV);
std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
}
Vector(Vector&& rhs) noexcept {
capacityV = std::move(rhs.capacityV);
sizeV = std::move(rhs.sizeV);
arr = std::move(rhs.arr);
}
Vector& operator = (const Vector& rhs) {
capacityV = rhs.capacityV;
sizeV = rhs.sizeV;
arr = AllocTraits::allocate(alloc, capacityV);
std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
}
Vector& operator = (Vector&& rhs) {
capacityV = std::move(rhs.capacityV);
sizeV = std::move(rhs.sizeV);
arr = std::move(rhs.arr);
}
T& operator [](std::size_t i) noexcept {
if (i < sizeV)
return arr[i];
else
throw std::out_of_range("Wrong index!");
}
const T& operator [](std::size_t i) const noexcept {
if (i < sizeV)
return arr[i];
else
throw std::out_of_range("Wrong index!");
}
T* data() noexcept {
return arr;
}
const T* data() const noexcept {
return arr;
}
void push_back(const T& value) {
++sizeV;
if (!arr) {
if (!capacityV)
capacityV = 128;
arr = AllocTraits::allocate(alloc, capacityV);
}
if (sizeV > capacityV) {
if(capacityV > UINT32_MAX - 256)
throw std::runtime_error("Vector overflowed!");
size_t tmpCap = capacityV;
capacityV = (sizeV / 128) * 256; //Увеличим capacityV
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
arr[sizeV - 1] = value;
}
void push_back(T&& value) {
++sizeV;
if (!arr) {
if (!capacityV)
capacityV = 128;
arr = AllocTraits::allocate(alloc, capacityV);
}
if (sizeV > capacityV) {
if (capacityV > UINT32_MAX - 256)
throw std::runtime_error("Vector overflowed!");
size_t tmpCap = capacityV;
capacityV = (sizeV / 128) * 256; //Увеличим capacityV
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
arr[sizeV - 1] = std::move(value);
}
void pop_back() {
--sizeV;
}
void resize(std::size_t size) {
if (this->sizeV == size)
return;
if (this->sizeV > size) {
this->sizeV = size;
}
else {
size_t tmpSize = size;
if (capacityV >= size) {
this->sizeV = size;
for (size_t i = tmpSize - 1; i < this->sizeV; i++)
arr[i] = 0;
}
else {
size_t tmpCap = capacityV;
capacityV = (size / 128) * 256; //that makes sense
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
this->sizeV = size;
for (size_t i = tmpSize - 1; i < this->sizeV; i++)
arr[i] = 0;
}
}
}
void reserve(std::size_t capacity) {
if (capacity > this->capacityV)
{
size_t tmpCap = capacity;
this->capacityV = capacity;
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
}
std::size_t size() const noexcept {
return sizeV;
}
std::size_t capacity() const noexcept {
return capacityV;
}
bool empty() const noexcept {
return (sizeV == 0);
}
};
}
Vector<int> v(CustomAllocator<int>());
你被 most vexing parse 击中了。 Vector<int> v(CustomAllocator<int>());
可以解析为变量声明 或 函数声明,语法更倾向于后者。因此,编译器认为 v
是一个函数,这就是您得到 "expression must have class type" 错误的原因——您只能对具有 class 类型的值调用方法,但是 v
是一个函数。
即使您使用以下选项之一修复了该错误:
// C++03 solution (extra parens)
Vector<int> v((CustomAllocator<int>()));
// C++11 solution (uniform initialization)
Vector<int> v{CustomAllocator<int>{}};
您的代码仍然无法达到您的预期,尽管它会 运行。 Vector<int>
与 Vector<int, std::allocator>
相同因此 v
仍将使用标准分配器。
为什么这不会导致编译错误?因为 CustomAllocator<int>
inherits std::allocator<int>
(不应该这样!),所以 std::allocator<int>
复制构造函数用于将自定义分配器分割成 std::allocator<int>
然后程序继续使用标准分配器。您的 CustomAllocator<int>
临时文件基本上转换为 std::allocator<int>
.
为了说明,上面的两个 "fixed" 示例 大致 等同于此代码(如果我们忽略一些与程序的可观察行为):
// Creates a custom allocator value.
CustomAllocator<int> a;
// Custom allocator is converted to std::allocator<int>.
std::allocator<int> b(a);
// std::allocator<int> is provided to the Vector constructor.
Vector<int> v(b);
正确的解决方法是指定第二个 Vector
类型参数,然后甚至不需要构造函数参数,因为默认构造函数会做正确的事情:
Vector<int, CustomAllocator> v;
问题是什么以及如何解决? 在不尝试压缩自定义分配器的情况下,我的向量乍一看可以正常工作。 我也很乐意指出我的代码中的任何错误。或者自定义向量或另一个容器的正确实现示例,这将对我有所帮助。 此代码无效:
using MyLib::Vector;
int main()
{
//Vector<int> v; //Works fine
Vector<int> v(CustomAllocator<int>());
for (size_t i = 0; i < 256; i++) {
v.push_back(i); //Error: "expression must have class type"
}
}
CustomAllocator 实现(应该没问题):
template <typename T>
class CustomAllocator : public std::allocator<T>
{
private:
using Base = std::allocator<T>;
public:
T* allocate(size_t count){
std::cout << ">> Allocating " << count << " elements" << std::endl;
return Base::allocate(count);
}
T* allocate(size_t count, const void* p)
{
std::cout << ">> Allocating " << count << " elements" << std::endl;
return Base::allocate(count, p);
}
void deallocate(T* p, size_t count)
{
if (p != nullptr)
{
std::cout << ">> Deallocating " << count << " elements" << std::endl;
Base::deallocate(p, count);
}
}
};
向量实现:
namespace MyLib
{
template <typename T,
template <typename Y> class Allocator = std::allocator>
class Vector
{
private:
std::size_t capacityV;
std::size_t sizeV;
Allocator<T> alloc;
T* arr;
public:
typedef Allocator<T> AllocatorType;
typedef Vector<T, Allocator> VectorType;
using AllocTraits = std::allocator_traits<Allocator<T>>;
public:
explicit Vector(const AllocatorType& allocator = AllocatorType()) {
capacityV = 0;
sizeV = 0;
alloc = allocator;
arr = nullptr;
}
Vector(const std::initializer_list<T>& values,
const AllocatorType& allocator = AllocatorType()) {
sizeV = values.size();
alloc = allocator;
if (sizeV < 128)
capacityV = 128;
else
capacityV = (sizeV / 128) * 256; //that makes sense
arr = AllocTraits::allocate(alloc, capacityV);
AllocTraits::construct(alloc, arr, capacityV);
std::copy(values.begin(), values.end(), arr);
}
~Vector() {
if (arr)
AllocTraits::deallocate(alloc, arr, capacityV);
}
Vector(const Vector& rhs) {
capacityV = rhs.capacityV;
sizeV = rhs.sizeV;
arr = AllocTraits::allocate(alloc, capacityV);
std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
}
Vector(Vector&& rhs) noexcept {
capacityV = std::move(rhs.capacityV);
sizeV = std::move(rhs.sizeV);
arr = std::move(rhs.arr);
}
Vector& operator = (const Vector& rhs) {
capacityV = rhs.capacityV;
sizeV = rhs.sizeV;
arr = AllocTraits::allocate(alloc, capacityV);
std::copy(rhs.arr, rhs.arr + rhs.sizeV, arr);
}
Vector& operator = (Vector&& rhs) {
capacityV = std::move(rhs.capacityV);
sizeV = std::move(rhs.sizeV);
arr = std::move(rhs.arr);
}
T& operator [](std::size_t i) noexcept {
if (i < sizeV)
return arr[i];
else
throw std::out_of_range("Wrong index!");
}
const T& operator [](std::size_t i) const noexcept {
if (i < sizeV)
return arr[i];
else
throw std::out_of_range("Wrong index!");
}
T* data() noexcept {
return arr;
}
const T* data() const noexcept {
return arr;
}
void push_back(const T& value) {
++sizeV;
if (!arr) {
if (!capacityV)
capacityV = 128;
arr = AllocTraits::allocate(alloc, capacityV);
}
if (sizeV > capacityV) {
if(capacityV > UINT32_MAX - 256)
throw std::runtime_error("Vector overflowed!");
size_t tmpCap = capacityV;
capacityV = (sizeV / 128) * 256; //Увеличим capacityV
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
arr[sizeV - 1] = value;
}
void push_back(T&& value) {
++sizeV;
if (!arr) {
if (!capacityV)
capacityV = 128;
arr = AllocTraits::allocate(alloc, capacityV);
}
if (sizeV > capacityV) {
if (capacityV > UINT32_MAX - 256)
throw std::runtime_error("Vector overflowed!");
size_t tmpCap = capacityV;
capacityV = (sizeV / 128) * 256; //Увеличим capacityV
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
arr[sizeV - 1] = std::move(value);
}
void pop_back() {
--sizeV;
}
void resize(std::size_t size) {
if (this->sizeV == size)
return;
if (this->sizeV > size) {
this->sizeV = size;
}
else {
size_t tmpSize = size;
if (capacityV >= size) {
this->sizeV = size;
for (size_t i = tmpSize - 1; i < this->sizeV; i++)
arr[i] = 0;
}
else {
size_t tmpCap = capacityV;
capacityV = (size / 128) * 256; //that makes sense
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
this->sizeV = size;
for (size_t i = tmpSize - 1; i < this->sizeV; i++)
arr[i] = 0;
}
}
}
void reserve(std::size_t capacity) {
if (capacity > this->capacityV)
{
size_t tmpCap = capacity;
this->capacityV = capacity;
T* buf = AllocTraits::allocate(alloc, capacityV);
std::move(arr, arr + sizeV - 1, buf);
AllocTraits::deallocate(alloc, arr, capacityV); //sizeof
arr = buf;
}
}
std::size_t size() const noexcept {
return sizeV;
}
std::size_t capacity() const noexcept {
return capacityV;
}
bool empty() const noexcept {
return (sizeV == 0);
}
};
}
Vector<int> v(CustomAllocator<int>());
你被 most vexing parse 击中了。 Vector<int> v(CustomAllocator<int>());
可以解析为变量声明 或 函数声明,语法更倾向于后者。因此,编译器认为 v
是一个函数,这就是您得到 "expression must have class type" 错误的原因——您只能对具有 class 类型的值调用方法,但是 v
是一个函数。
即使您使用以下选项之一修复了该错误:
// C++03 solution (extra parens)
Vector<int> v((CustomAllocator<int>()));
// C++11 solution (uniform initialization)
Vector<int> v{CustomAllocator<int>{}};
您的代码仍然无法达到您的预期,尽管它会 运行。 Vector<int>
与 Vector<int, std::allocator>
相同因此 v
仍将使用标准分配器。
为什么这不会导致编译错误?因为 CustomAllocator<int>
inherits std::allocator<int>
(不应该这样!),所以 std::allocator<int>
复制构造函数用于将自定义分配器分割成 std::allocator<int>
然后程序继续使用标准分配器。您的 CustomAllocator<int>
临时文件基本上转换为 std::allocator<int>
.
为了说明,上面的两个 "fixed" 示例 大致 等同于此代码(如果我们忽略一些与程序的可观察行为):
// Creates a custom allocator value.
CustomAllocator<int> a;
// Custom allocator is converted to std::allocator<int>.
std::allocator<int> b(a);
// std::allocator<int> is provided to the Vector constructor.
Vector<int> v(b);
正确的解决方法是指定第二个 Vector
类型参数,然后甚至不需要构造函数参数,因为默认构造函数会做正确的事情:
Vector<int, CustomAllocator> v;