Безопасное размещение нового и явного вызова деструктора - PullRequest
1 голос
/ 08 июня 2010

Это пример моих кодов:

template <typename T> struct MyStruct {
    T object;
}

template <typename T> class MyClass {
    MyStruct<T>* structPool;
    size_t structCount;

    MyClass(size_t count) {
        this->structCount = count;
        this->structPool  = new MyStruct<T>[count];
        for( size_t i=0 ; i<count ; i++ ) {
            //placement new to call constructor
            new (&this->structPool[i].object) T(); 
        }
    }

    ~MyClass() {
        for( size_t i=0 ; i<this->structCount ; i++ ) {
            //explicit destructor call
            this->structPool[i].object.~T(); 
        }
        delete[] this->structPool;
    }
}

У меня вопрос, это безопасный способ сделать? Я делаю какую-то скрытую ошибку в каком-то состоянии? Будет ли это работать для каждого типа объекта (POD и не POD)?

Ответы [ 4 ]

7 голосов
/ 08 июня 2010

Нет, потому что ваш конструктор и деструктор вызываются дважды. Потому что у вас есть это:

template <typename T> struct MyStruct {
    T object;
}

Когда вы создаете MyStruct<T>, компиляция создаст внутренний T, а когда вы удалите объект, внутренний T автоматически вызовет деструктор.

В этом примере нет необходимости размещать новый или явный вызов деструктора.

Размещение new будет полезно, если вы выделите необработанную память. Например, если вы изменили свой новый на:

this->structPool  = new char[sizeof(T) * count];

тогда вы захотите разместить новый и вызвать вызов деструктора.

1 голос
/ 08 июня 2010

Нет, это, конечно, даже удаленно безопасный способ сделать это.Когда вы делаете new MyStruct<T>[count] для не POD T, каждый объект MyStruct<T> в массиве уже создается по умолчанию, что означает, что конструктор для члена object вызывается автоматически.Затем вы пытаетесь выполнить конструкцию на месте (путем инициализации значения) поверх этого.Результирующее поведение не определено.

Та же проблема существует с удалением.

Чего вы пытаетесь достичь?Просто сделайте new MyStruct<T>[count]() (обратите внимание на дополнительный пустой ()), и он уже выполнит инициализацию значения для каждого элемента массива (именно то, что вы пытаетесь сделать «вручную» впоследствии).Почему вы чувствуете, что должны делать это при построении на месте?

Аналогично, когда вы делаете

delete[] this->structPool;

, он автоматически вызывает деструктор для каждого члена MyStruct<T>::object в массиве.Не нужно делать это вручную.

0 голосов
/ 08 июня 2010
template <typename T> class MyClass {
    void* structPool;
    size_t structCount;

    MyClass(size_t count)
      : structPool(new char[sizeof(T)*count], structCount(count)
    {
        //placement new to call constructor
        for( size_t i=0 ; i<count ; i++ )
            new (structPool+i*sizeof(T)) T(); 
    }

    ~MyClass() {
        //explicit destructor call
        for( size_t i=0 ; i<structCount ; i++ )
            reinterpret_cast<T*>(structPool+i*sizeof(T))->~T(); 
        delete[] structPool;
    }
}

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

Взгляните на std::vector в вашей любимой реализации std lib, чтобы увидеть, как это сделать правильно. Однако это приводит к вопросу: Почему вы хотите сделать это в первую очередь?
A std::vector уже все это делает, делает это правильно, вы можете использовать его «из коробки», и каждый, кто посмотрит на ваш код, сразу поймет это:

template <typename T> class MyClass {
    std::vector<T> data_;
    MyClass(size_t count) : data() {data.resize(count);}
    //~MyClass() not needed
}
0 голосов
/ 08 июня 2010
  1. Запоминание нового всегда вызовет конструктор, независимо от того, является ли он размещением или нет.

- Таким образом, ваш код использовал новый дважды.Это вызовет конструктор дважды.Если вы хотите избежать этого, либо:

Измените своего первого нового на malloc (или любой другой тип размещения)

Удалите свое второе новое место размещения

Чтобы удалить объекты в массиве, лучше всего: вызывать деструктор каждого объекта;освободить память.

- Таким образом, вы можете сделать следующее:

Удалить объект с помощью delete [], если вы используете new []

Вызвать каждый деструктори сделайте стиль C бесплатным, если вы используете malloc и размещаете новый

...