Как я могу разрешить свой шаблон Vector разрешать типы без конструктора по умолчанию (пустой)? - PullRequest
0 голосов
/ 13 января 2012

Я создаю шаблонный класс 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, ноЯ делаю это, чтобы узнать больше о языке и столкнуться с такими случаями)

Ответы [ 5 ]

3 голосов
/ 13 января 2012

Причина, по которой это не работает для типов без конструкторов по умолчанию, заключается в следующей строке:

    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 должен сделать довольно много бухгалтерии, чтобычто он делает эффективно и безопасно.Вот почему мы призываем людей использовать стандартные контейнеры вместо того, чтобы выкатывать свои собственные, если вы действительно не знаете, что делаете.Создание эффективного, безопасного и полезного векторного класса - это огромное обязательство.

Чтобы понять, что с этим связано, взгляните на статью под названием "Исключительная безопасность: концепции иТехника "" Бьярна Страуструпа, в которой обсуждается реализация "простого вектора" (раздел 3.1).Вы увидите, что это не тривиальная вещь для реализации.

1 голос
/ 13 января 2012

std :: vector не использует конструктор по умолчанию, потому что каждый раз, когда ему нужно что-то сконструировать, он использует конструктор копирования (или любой другой указанный вами конструктор, спасибо Kerrek SB, обсуждение ниже). Таким образом, вы могли бы заставить ваш векторный класс работать, не используя конструктор по умолчанию в таких строках, как:

m_Data = new Type[m_Capacity];

Вы можете использовать новое размещение, которое позволяет вам построить объект в уже выделенной памяти. Это позволяет вам вызывать любой конструктор, который вы пожелаете, например конструктор копирования. Это делается так:

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 для создания двух экземпляров на месте, без вызова конструктора по умолчанию: скорее, мы вызываем конструктор копирования. Таким образом, мы можем создать множество экземпляров в массиве без вызова конструктора по умолчанию.

Наконец, вам нужно будет удалить исходное размещение, а не те, которые были сделаны с использованием нового размещения. Поскольку освобождение исходного выделения не вызовет деструкторы для экземпляров, которые вы создали в блоке памяти, вам нужно будет явно вызвать их деструкторы.

1 голос
/ 13 января 2012

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);

Затем, когда у вас будет доступная необработанная память, вам потребуется способ создания объектов в ней для реализации остальной векторной функциональности.Это делается с помощью размещения-нового, который является формой new, которая просто вызывает конструктор по определенному адресу:

Type* obj = ::new(address) Type(arguments);

Затем уничтожение объектов выполняется явным вызовом деструктора, потому чтоВы не хотите освобождать память каждый раз, когда элемент уничтожается.

obj->~T();
0 голосов
/ 13 января 2012

Если вы удалите m_Data = new Type[m_Capacity]; из вашего конструктора и отложите это создание на потом, оно будет работать. Однако, как уже было отмечено, std::vector имеет то же правило: если бы у вас было std::vector<SomeStruct> test1(10);, вы бы получили ту же ошибку.

Также, void main() ПЛОХО. Всегда должно быть не менее int main.

0 голосов
/ 13 января 2012

вставьте это в свой код:

SomeStruct() = default;

, который создает конструктор по умолчанию.

или это:

SomeStruct() {}

то же самое.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...