我正在创建一个模板化的Vector类,但是,当将它的用法与std :: vector之类的东西进行比较时,我注意到它不允许没有默认(emtpty)构造函数的struct \ classes。我将得到的错误是
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,但我这样做是为了更多地了解这种语言,并遇到这样的情况)
答案 0 :(得分:3)
这对于没有默认构造函数的类型不起作用的原因是因为这一行:
m_Data = new Type[m_Capacity];
以上几行基本上做了两件事:分配足够的内存来保存m_Capacity
的{{1}}个实例,然后构建每个Type
以便它们可以使用。由于您无法通过此Type
语法实际提供任何构造函数参数,因此在使用时需要使用默认构造函数。
new[]
(以及其他标准容器)处理此问题的方法是分离内存分配过程和构建过程。也就是说,std::vector
通过请求大块内存并且“没有”来分摊内存分配的成本。然后std::vector
使用placement new
直接在该内存中构造对象。
所以这样的事情可能会发生在std::vector
:
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
课程需要进行相当多的簿记才能使其有效且安全。这就是为什么我们敦促人们使用标准容器而不是推出自己的容器,除非你真的知道你正在做什么。制作一个高效,安全和有用的矢量类是巨大的承诺。
要了解所涉及的内容,请查看Bjarne Stroustrup撰写的一篇名为"Exception Safety: Concepts and Techniques"的论文,该论文讨论了“简单向量”实现(第3.1节)。你会发现实施它并不是一件小事。
答案 1 :(得分:1)
new Type[m_Capacity]
创建m_Capacity
类Type
个对象的数组。那不是你想要的。你想要一个空的向量,有足够的原始内存用于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();
答案 2 :(得分:1)
std :: vector不使用默认构造函数,因为每次需要构造某些东西时,它都会使用复制构造函数(或者您指定的任何构造函数,感谢Kerrek SB,下面的讨论)。因此,您可以通过不使用以下行中的默认构造函数来使您的矢量类工作:
m_Data = new Type[m_Capacity];
您可以使用placement 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'Type'的内存。然后我们使用placement new来构造两个实例,而不调用默认的构造函数:相反,我们调用复制构造函数。通过这种方式,我们可以在不调用默认构造函数的情况下在数组中构造许多实例。
最后,您需要删除原始分配,而不是使用placement new进行分配。因为取消分配原始分配不会为您在内存块中创建的实例调用析构函数,所以您需要显式调用它们的析构函数。
答案 3 :(得分:0)
将其放入您的代码中:
SomeStruct() = default;
创建默认构造函数。
或者这个:
SomeStruct() {}
同样的事情。
答案 4 :(得分:0)
如果从构造函数中删除m_Data = new Type[m_Capacity];
,并将此创建延迟到以后,它将起作用。但是,正如已经指出的那样,std::vector
具有相同的规则:如果您有std::vector<SomeStruct> test1(10);
,则会得到相同的错误。
此外,void main()
是不好的。它应始终至少为int main
。