Только типы с тривиальным деструктором подходят для хранения для размещения новых? - PullRequest
0 голосов
/ 02 июля 2018

В примерах для размещения новых часто используются беззнаковые массивы в качестве основного хранилища. Шаги могут быть:

  1. создать массив без знака с новым
  2. создать объект в этом хранилище с размещением нового
  3. использовать объект
  4. уничтожить объект
  5. вызовите delte для массива unsigned char, чтобы освободить массив

Точка 5., кажется, работает, только если мы используем тип для основного хранилища с тривиальным деструктором. В противном случае мы бы вызвали деструктор базового типа хранилища, но без существующего объекта. Технически, мы уничтожаем группу неподписанных символов, которых нет, и нам повезло, что деструктор типа unsigned char тривиален и поэтому не работает.

А как насчет следующего кода:

struct A{ /* some members... */ };
struct B{ /* some members... B shall be same size as A */ };

int main()
{    
    auto ptr_to_a = new A; // A object lives @ ptr_to_a    
    ptr_to_a->~A(); // A object destroyed. no object living @ ptr_to_a, but storage is preserved
    new (ptr_to_a) B; // B object living @ ptr_to_a.    
    std::launder(reinterpret_cast<b*>(ptr_to_a))->/*...*/; // use B. for this purpose we need std::launder in C++17 or we would store the pointer returned by the placement new and use it without std::launder
    std::launder(reinterpret_cast<b*>(ptr_to_a))->~B(); // B object destroyed. no object living @ ptr_to_a, but storage is preserved

    // at this point there is no object living @ ptr_to_a, but we need to hand back the occupied storage.

    // a)
    delete ptr_to_a; // undefined behavior because no object is sitting @ ptr_to_a

    // b)
    new (ptr_to_a) A; // create an object again to make behavior defined. but this seems odd.
    delete ptr_to_a;

    // c)
    // some method to just free the memory somehow without invoking destructors?

    return 0;
}

На https://en.cppreference.com/w/cpp/language/lifetime написано: Как особый случай, объекты могут быть созданы в массивах без знака char или std :: byte (в этом случае говорят, что массив предоставляет хранилище для объекта), если ....

Означает ли это, что разрешено использовать только размещение новых в неподписанных массивах char и byte и поскольку у них есть тривиальный деструктор, мой пример кода устарел?

В противном случае, как насчет моего примера кода? Является ли вариант б) единственным действительным решением?

Редактировать: второй экзамен:

struct A{ /* some members... */ };
struct alignas(alignof(A)) B{ /* some members... */ };

int main()
{    
    static_assert(sizeof(A) == sizeof(B));
    A a;      
    a.~A();    
    auto b_ptr = new (&a) B; 
    b_ptr->~B();    
    return 0;
    // undefined behavior because a's destructor gets called but no A object is "alive" (assuming non trivial destructor)
    // to make it work, we need to placement new a new A into a?
}

Ответы [ 2 ]

0 голосов
/ 02 июля 2018

По большей части да.

Однако вы можете сделать что-то подобное.

struct placed {
  char stuff[100];
};
struct stupid {
  std::aligned_storage_t<sizeof(placed), alignof(placed)> data;
  ~stupid() {
    std::cout << "stupid gone\n";
  }
};

int main() {
  auto* pstupid = new stupid;
  auto* pplaced = ::new( (void*)&pstupid->data ) placed;
  pplaced->~placed();
  auto* pstupid2 = ::new( (void*)pstupid ) stupid;
  delete pstupid2;
}

но это, как следует из названия типа, довольно глупо. И чрезвычайно трудно сделать исключение безопасным без множества noexcept гарантий, которые я не включил выше.

Я также не совсем уверен, является ли delete pstupid2 допустимым, так как он был создан путем размещения нового, а не с помощью простого нового выражения.

0 голосов
/ 02 июля 2018

Как правило, вы не будете использовать хранилище, возвращаемое выделением несвязанного класса A, для помещения B. Вам даже не нужно выделять вообще

int main ()
{
    char storage[sizeof(B)];
    std::aligned_storage<sizeof(B), alignof(B)>::type aligned_storage;

    auto b_ptr1 = new (&storage) B; // potentially unaligned
    auto b_ptr2 = new (&aligned_storage) B; // guaranteed safe

    // use b_ptr1, b_ptr2

    b_ptr1->~B();
    b_ptr2->~B();
    // storage ceases to exist when main returns
}

Если вам нужно динамически распределить, я бы предложил обернуть хранилище в структуру держателя, чтобы вы не заканчивали время жизни того, что вы new изд.

struct B_holder
{
    std::aligned_storage<sizeof(B), alignof(B)>::type storage;
    B * make_B() { return new(&storage) B; }
}

int main()
{
    auto holder = std::make_unique<B_holder>();
    auto * B_ptr = B_holder->make_B();

    // use B_ptr

    B_ptr->~B();
    // holder ceases to exist when main returns
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...