提问人:Herman 提问时间:9/18/2023 最后编辑:πάντα ῥεῖHerman 更新时间:9/19/2023 访问量:27
无法序列化和设置容器类的 DebuggerDisplayAttribute(s)
Unable to serialize and set DebuggerDisplayAttribute(s) for container class
问:
我正在为一个带有预分配内存块的自定义内存管理器的项目做一个向量实现,并且我几乎成功地使所述向量执行基本功能,如push_back、擦除、保留、begin() 和 end() 以允许每个循环,等等,但遗憾地(和期望)悬停它只显示指向的第一个对象,没有别的。
虽然不是严格意义上的 nessecary,但能够将此实现悬停在常规 std::vector 上仍然非常有帮助。这是可以做到的吗?我发现了一些关于Visual Studio中变量的自定义属性的页面,但未能真正实现它们。
我已经提供了代码,因为它目前位于问题的底部。我认为,这个问题的代码中唯一重要的部分是顶部。尝试编译时,我收到三个错误:E2020 - 此处不能使用托管 nullptr 类型、C2337 - 'Serializable':找不到属性和 C2337'NonSerialized':找不到属性(每个成员变量都适用)。如果我删除类声明上方的 [Serializable],则会出现 C3115 'System::D iagnostics::D ebuggerDisplayAttribute': 'ML_Vector' 上不允许此属性,而不是 Serializable 错误。
我已将 /clr 添加到项目的公共语言运行时支持字段。
这似乎是一个相当晦涩的话题,因为我还没有真正找到任何解释该主题的视频,也不知道从这里开始。
#pragma once
#include "MemLib\MemLib.hpp"
#include <cinttypes>
#include <stdexcept>
#ifdef _DEBUG
using namespace System::Diagnostics;
#endif // _DEBUG
template<typename T>
#ifdef _DEBUG
[Serializable]
[DebuggerDisplayAttribute("Size={m_size}")] // Supposed to act as a proof of concept
struct ML_Vector
{
private:
// Pool pointer to internal data
[NonSerialized]
PoolPointer<T> m_data;
// Due to our memory usage restriction, a size larger than 2^30 would be guaranteed to exceed memory limits
[NonSerialized]
uint32_t m_size = 0;
// Due to our memory usage restriction, a size larger than 2^30 would be guaranteed to exceed memory limits
[NonSerialized]
uint32_t m_capacity = 0;
// size of the internal type
[NonSerialized]
uint16_t m_tSize = 0;
#else // _DEBUG
struct ML_Vector
{
private:
// Pool pointer to internal data
PoolPointer<T> m_data;
// Due to our memory usage restriction, a size larger than 2^30 would be guaranteed to exceed memory limits
uint32_t m_size = 0;
// Due to our memory usage restriction, a size larger than 2^30 would be guaranteed to exceed memory limits
uint32_t m_capacity = 0;
// size of the internal type
uint16_t m_tSize = 0;
#endif
public:
T* begin() const
{
return &(m_data[0]);
}
T* end() const
{
return &(m_data[m_size]);
}
const uint32_t& size() const
{
return m_size;
}
const PoolPointer<T>& data() const
{
return m_data;
}
// Reserve a new capacity for the vector
uint32_t reserve(const uint32_t& capacity)
{
if (capacity < m_capacity)
{
throw std::invalid_argument("Capacity too small! ML_Vector.reserve() cannot be called to reduce the capacity of the vector!");
std::terminate();
}
// Provide a temporary copy of the data
T* temp = (T*)MemLib::spush(m_capacity * m_tSize);
std::memcpy(temp, &(*m_data), m_capacity * m_tSize);
// Free the old pool pointer and allocate a new one
MemLib::pfree(m_data);
m_data = MemLib::palloc(capacity);
// Copy the data over to the new location and pop the temp from the stack
std::memcpy(&(*m_data), temp, m_capacity * m_tSize);
MemLib::spop();
// Inform the new capacity
return m_capacity = capacity;
};
// Clear the vector
void clear()
{
// No need to actually clear data, just setting the size to 0 is enough
m_size = 0;
}
// Push an item into the back of the vector, returns the index of that item
uint32_t push_back(const T& item)
{
// if the capacity of the vector is less than the size of the vector, reserve a larger chunk of memory
if (m_capacity <= m_size + 1)
{
reserve(m_capacity * 2);
}
// Set data at location
m_data[m_size] = item;
// Return the index of the newly pushed object
return m_size++;
};
// Remove an item in the vecotr by index
// This operation is much slower than pop_back, and should not be used if pop_back could simply be used instead
uint32_t erase(const uint32_t& idx)
{
if (idx < 0 || m_size <= idx)
{
throw std::out_of_range("Index provided for ML_Vector is out of range!");
std::terminate();
}
// Overwrite
std::memcpy(&(m_data[idx]), &(m_data[idx + 1]), (m_size - idx) * m_tSize);
return --m_size;
};
// Pop and return the back most item of the vector
T pop_back()
{
if (m_size <= 0)
{
throw std::out_of_range("Out of range exception for ML_Vector at pop_back(), vector already empty!");
std::terminate();
}
// No need to actually remove the data in any "real" way, just mark it as empty
return m_data[--m_size];
};
T& operator*()
{
return (*m_data);
};
T& operator[](const uint32_t& idx)
{
if (idx < 0 || m_size <= idx)
{
throw std::out_of_range("Index provided for ML_Vector is out of range!");
std::terminate();
}
return m_data[idx];
};
ML_Vector& operator=(const ML_Vector& other)
{
if (false == m_data.IsNullptr())
MemLib::pfree(m_data);
m_data = other.m_data;
m_capacity = other.m_capacity;
m_size = other.m_size;
m_tSize = other.m_tSize;
return *this;
}
// Initiate an ML_Vector<T> with a number of T objects, can be called as such to emulate normal C++ style coding
// ML_Vector<T>() = { args };
template<typename... Types>
ML_Vector(Types... args)
{
// Set capacity
m_capacity = sizeof...(args);
// Allocate to memory pool
if (false == m_data.IsNullptr())
MemLib::pfree(m_data);
m_data = MemLib::palloc(m_capacity);
// Set the individual item size
m_tSize = sizeof(T);
// Set items
for (auto item : { args... } )
{
/*T test = item;*/
push_back(item);
//ZeroMemory(item, sizeof(item));
}
};
};
答:
我设法通过简单地放弃System::D iagnotics并找出.natvis文件来解决这个问题。我在项目中添加了一个(添加新项目 -> Visual C++ -> Utility -> Debugger Visualization File)。
下面的代码目前运行良好,至少让我们看到了双指针背后的内部结构。
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="ML_Vector<*>">
<!--<DisplayString>{{ size={_Mylast - _Myfirst} }}</DisplayString>-->
<Expand>
<Item Name="[Size]" ExcludeView="simple">m_size</Item>
<Item Name="[capacity]" ExcludeView="simple">m_capacity</Item>
<ArrayItems>
<Size>m_size</Size>
<ValuePointer>*(m_data.m_pp)</ValuePointer>
</ArrayItems>
</Expand>
</Type>
评论