如何在没有默认(空)构造函数的情况下允许我的模板化 Vector 允许类型
How can I allow my templated Vector allow types without a default (empty) constructor?
我正在创建一个模板化的 Vector 类,但是,当将其使用与 std::vector 之类的东西进行比较时,我注意到它不允许没有默认 (emtpty( 构造函数的结构\类。我会得到的错误是
error C2512: 'SomeStruct' : no appropriate default constructor available
: while compiling class template member function 'Vector<Type>::Vector(void)'
: see reference to class template instantiation 'Vector<Type>' being compiled
但是,如果我去使用 std::vector,这是允许的。这是我的测试用例
struct SomeStruct
{
SomeStruct(int a){}
};
template<typename Type>
class Vector
{
public:
Vector();
protected:
Type* m_Data;
unsigned int m_Count;
unsigned int m_Capacity;
};
template<typename Type>
Vector<Type>::Vector()
{
m_Capacity = 0;
m_Count = 0;
m_Data = new Type[m_Capacity];
}
void main()
{
Vector<SomeStruct> test1;
}
如何在没有默认(空(构造函数的情况下允许我的模板化 Vector 允许类型?
(我知道我可以使用std::vector,但我这样做是为了更多地了解该语言,并遇到这样的情况(
没有默认构造函数的类型的原因是因为以下行:
m_Data = new Type[m_Capacity];
上行基本上做了两件事:分配足够的内存来容纳m_Capacity
Type
实例,然后构造每个Type
以便它们准备好使用。由于您实际上无法通过此new[]
语法提供任何构造函数参数,因此在使用它时需要默认构造函数。
std::vector
(和其他标准容器(处理这个问题的方法是将内存分配过程和构造过程分开。也就是说,std::vector
通过请求包含"无"的大块内存来摊销内存分配的成本。然后std::vector
使用放置new
直接在该内存中构造对象。
所以这样的事情可能会在std::vector
内发生:
// HUGE SIMPLICATION OF WHAT HAPPENS!!!
// EXPOSITION ONLY!!!
// NOT TO BE USED IN ANY PRODUCTION CODE WHATSOEVER!!!
// (I haven't even considered exception safety, etc.)
template<typename T>
class Vector
{
private:
T* allocate_memory(std::size_t numItems)
{
// Allocates memory without doing any construction
return static_cast<T*>(::operator new(sizeof(T)*numItems));
}
void deallocate_memory()
{
::operator delete(buffer);
}
// ...
public:
void push_back(const T& obj)
{
if(theresNotEnoughRoom()) {
std::size_t newCapacity = calculateNewCapacity();
T* temp = allocate_memory(newCapacity);
copyItemsToNewBuffer(temp);
deallocate_memory(buffer);
buffer = temp;
bufferEnd = temp+newCapacity;
}
new (bufferEnd) T(obj); // Construct a new instance of T at end of buffer.
++bufferEnd;
}
void pop_back()
{
if(size() > 0) {
--bufferEnd;
bufferEnd->~T();
}
}
// ...
private:
T* buffer;
T* bufferEnd;
// ...
};
所以这里发生的事情是,我们假设的Vector
类分配了一个相对较大的内存板,然后当项目被推送或插入时,该类会在内存中放置新位置。因此,这消除了默认的构造函数要求,因为除非调用方请求,否则我们实际上不会构造任何对象。
正如您已经看到的,std::vector
类需要做相当多的簿记才能使其工作高效和安全。这就是为什么我们敦促人们使用标准容器而不是推出自己的容器,除非您真的知道自己在做什么。制作一个高效、安全和有用的向量类是一项艰巨的任务。
要了解所涉及的内容,请查看Bjarne Stroustrup的一篇名为"异常安全:概念和技术"的论文,该论文讨论了"简单向量"实现(第3.1节(。您会发现实现这不是一件微不足道的事情。
new Type[m_Capacity]
创建类型为 Type
的 m_Capacity
对象的数组。那不是你想要的。您需要一个空向量,具有足够的原始内存来存储m_Capacity
对象。你不想要对象,你只想要内存。
有几种工具可以在C++中获取原始内存:分配器,::operator new
或malloc
。我建议现在使用::operator new
。
void* storage = ::operator new(sizeof(Type) * m_Capacity);
// and deallocation
::operator delete(storage);
然后,一旦你有原始内存可用,你将需要一种方法来构造其中的对象来实现其余的矢量功能。这是使用 placement-new 完成的,这是一种new
形式,它只是在某个地址调用构造函数:
Type* obj = ::new(address) Type(arguments);
然后,通过显式调用析构函数来完成对象的销毁,因为您不希望每次销毁元素时都释放内存。
obj->~T();
std::vector 不使用默认构造函数,因为每次需要构造某些东西时,它都会使用复制构造函数(或您指定的任何构造函数,感谢 Kerrek SB,下面的讨论(。因此,您可以通过不使用默认构造函数来使向量类正常工作,例如:
m_Data = new Type[m_Capacity];
您可以使用放置 new,它允许您在已分配的内存中构造对象。这允许您调用所需的任何构造函数,例如复制构造函数。这是这样完成的:
int typeSize = sizeof(Type);
char* buffer = new char[typeSize * 2];
Type* typeA = new(buffer) Type(default_value);
Type* typeB = new(&buffer[typeSize]) Type(default_value);
这里有两件事值得注意:我们调用 new 一次,分配一段大小等于 2 个"类型"的内存。然后,我们使用放置 new 就地构造两个实例,而不调用默认构造函数:相反,我们调用复制构造函数。这样,我们可以在数组中构造许多实例,而无需调用默认构造函数。
最后,您需要删除原始分配,而不是使用新放置位置的分配。由于解除分配原始分配不会调用在内存块中创建的实例的析构函数,因此您需要显式调用其析构函数。
把它放到你的代码中:
SomeStruct() = default;
这将创建默认构造函数。
或者这个:
SomeStruct() {}
同样的事情。
如果从构造函数中删除m_Data = new Type[m_Capacity];
,并将此创建推迟到以后,它将起作用。但是,正如已经指出的那样,std::vector
具有相同的规则:如果您有std::vector<SomeStruct> test1(10);
,则会遇到相同的错误。
此外,void main()
是不好的。它应该始终至少是 int main
.
- 奇怪的(对我来说)返回声明 - 在谷歌上找不到任何关于它的信息
- 0-1背包代码中的错误.我的代码中有什么错误
- 我不明白为什么我声明一个空的内部结构并将其传递给构造函数
- 如何设置一个范围来提取我想要获得的信息
- 为什么在我的函数类型后使用引用运算符 (&) 允许我修改它返回的值?
- 在c++中为我自己的基于指针的数组分配内存的正确方法
- 我无法在 qt 的C++代码中定义字符串
- 如何使 std::sort 在 std::swap 和我的命名空间的模板化交换之间没有名称冲突?
- 当我使用 C++ 中的 C# dll 来使用 Selenium 时,存在异常处理问题
- C++出现控制台错误.我无法识别源代码的问题
- 我可以在 Arduino 的库文件夹之外安装库吗?
- Opencv 恢复到比我设置的更高的分辨率
- 反向功能超出了我的 cpp 程序的范围
- 当我输入字符类型的数字时,为什么我无法获得整数?
- 我希望定义两个具有相同代码的不同名称的库
- 移动赋值运算符;尝试引用已删除的函数.我该如何解决这个问题?
- 我应该在简单的策略游戏中为各个派系使用类吗 - C++
- C++ 每次运行程序时我都会"nan"输出的问题
- 为什么当我输入较大的数字时,我的程序会到达文件末尾?
- 为什么我在 AVR 中的中断无法正常工作?