如何在没有默认(空)构造函数的情况下允许我的模板化 Vector 允许类型

How can I allow my templated Vector allow types without a default (empty) constructor?

本文关键字:我的 许我的 类型 许类型 Vector 默认 构造函数 情况下      更新时间:2023-10-16

我正在创建一个模板化的 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]创建类型为 Typem_Capacity 对象的数组。那不是你想要的。您需要一个空向量,具有足够的原始内存来存储m_Capacity对象。你不想要对象,你只想要内存。

有几种工具可以在C++中获取原始内存:分配器,::operator newmalloc 。我建议现在使用::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 .