повторное использование пространства объекта другим объектом - PullRequest
3 голосов
/ 10 июля 2019

Я искал для повторного использования выделенного пространства в базовом классе из этого указателя, и C ++ Standard не одобряет. Однако формулировка стандарта кажется неверной. Он ставит условие «и перед тем, как хранилище, в котором занят объект, используется повторно или освобождается», но оно явно используется повторно в своем фрагменте кода. Где я ошибаюсь?

void B::mutate() {
  new (this) D2;    // reuses storage — ends the lifetime of *this!! REUSED AS WELL SO CONDITION SO RESTRICTIONS DON'T HOLD ANYMORE!
  f();              // undefined behavior

До того, как началось время жизни объекта, но после того, как было выделено хранилище, которое будет занимать объект41, или после того, как истек срок жизни объекта и до того, как хранилище, которое занимал объект, используется повторно или освобождается, любой указатель, который представляет адрес места хранения, где объект будет или был расположен, может использоваться, но только ограниченным образом. О строящемся или разрушаемом объекте см. [Class.cdtor]. В противном случае такой указатель ссылается на выделенное хранилище ([basic.stc.dynamic.deallocation]), и использование указателя, как если бы указатель имел тип void *, является четко определенным. Переадресация через такой указатель разрешена, но результирующее lvalue может использоваться только ограниченным образом, как описано ниже. Программа имеет неопределенное поведение, если:

(6.1) объект будет или имел тип класса с нетривиальным деструктором, а указатель используется в качестве операнда выражения удаления

(6,2) указатель используется для доступа к нестатическому члену данных или для вызова нестатической функции-члена объекта, или

(6,3) указатель неявно преобразуется ([conv.ptr]) в указатель на виртуальный базовый класс или

(6.4) указатель используется как операнд static_cast, за исключением случаев, когда преобразование является указателем на cv void или указателем на cv void, а затем на указатель на cv char, cv unsigned char или cv std :: byte ([ cstddef.syn]) или

(6,5) указатель используется в качестве операнда dynamic_cast.

[Пример:

    #include <cstdlib>
    struct B {
      virtual void f();
      void mutate();
      virtual ~B();
    };

    struct D1 : B { void f(); };
    struct D2 : B { void f(); };

   /* RELEVANT PART STARTS */ void B::mutate() {
      new (this) D2;    // reuses storage — ends the lifetime of *this
      f();              // undefined behavior 
      /* RELEVANT PART ENDS */
      ... = this;       // OK, this points to valid memory
    }

   void g() {
      void* p = std::malloc(sizeof(D1) + sizeof(D2));
      B* pb = new (p) D1;
      pb->mutate();
      *pb;              // OK: pb points to valid memory
      void* q = pb;     // OK: pb points to valid memory
      pb->f();          // undefined behavior, lifetime of *pb has ended
    }

Ответы [ 2 ]

3 голосов
/ 10 июля 2019

Когда вы делаете

f();

в функции-члене вы действительно делаете

this->f();

Так в примере, когда они делают

new (this) D2; 

заканчивает время жизни указателя на вещь на this и создает новый D2 на своем месте. Это заставляет вас думать, что this->f(); - это нормально, поскольку this теперь указывает на объект, у которого началось время жизни, но вы забываете, что this - указатель на объект, у которого закончилось время жизни. Вы не можете использовать его для ссылки на новый объект, который вы создали.

Чтобы иметь возможность легально вызвать f(), вам нужно захватить указатель, возвращенный new, и использовать его для доступа к новому объекту

void B::mutate() {
  auto np = new (this) D2;    // reuses storage — ends the lifetime of *this
  f();              // undefined behavior**
  np->f()           // OK, np points to a valid object
  ... = this;       // OK, this points to valid memory
}
2 голосов
/ 10 июля 2019

но он явно используется в своем фрагменте кода.

new (this) D2;    // reuses storage — ends the lifetime of *this
f();              // undefined behavior**
... = this;       // OK, this points to valid memory

Правильно. В связи с повторным использованием памяти, применяется условие «В противном случае»:

... В противном случае такой указатель ссылается на выделенное хранилище ([basic.stc.dynamic.deallocation]), и использование указателя, как если бы указатель имел тип void *, является четко определенным.

Вызов f() через void* невозможен, поэтому это предложение не разрешено. В противном случае вызов функций-членов объекта, срок жизни которого закончился, не определен (за пределами деструктора).

... = this; с другой стороны - это то, что можно сделать с помощью void*.

Обратите внимание, что (new (this) D2)->f() будет четко определено.

...