Задержка строительства истинного базового класса с размещением нового - PullRequest
1 голос
/ 03 ноября 2011

Я спрашиваю, является ли (и почему) следующий подход: а) законным и б) моральным.Я прошу с акцентом на C ++ 03, но приветствуются также заметки на C ++ 11.Идея состоит в том, чтобы не допустить, чтобы производные классы, которые сами могли бы быть конструктивными по умолчанию, реализовывали глупые B::B(int foo) : A(foo) {} конструкторы.

class Base {
  private:
    int i;
    Base(int i) : i(i) {}
  protected:
    Base() {}
  public:
    static Base* create(int i);
};
class Derived : public Base {
};

Base* Base::create(int i) {
  Derived* d = new Derived();
  Base* b = static_cast<Base*>(d);
  delete b;
  new(b) Base(i);
  return d;
}

Моя интуиция говорит мне, что здесь что-то подозрительно.Если какой-либо класс Derived обращается к Base членам его конструктора, я хочу быть где-то еще, но в противном случае у меня возникают проблемы с поиском веских причин, по которым такой подход плохой.

В любом случае, если вы считаете, что этоприемлемый подход, как бы иметь дело с ссылочными элементами (что-то вроде int& Base::j)?

Примечание: Это дополнительный вопрос к Как я могу подделать наследование конструктора вC ++ 03? .


Редактировать : Я, должно быть, отвлекся при публикации вопроса.Конечно, вместо delete b я имел в виду b->~Base().Я виню низкий уровень сахара в крови!

Ответы [ 2 ]

3 голосов
/ 03 ноября 2011

Код неверен и вызывает неопределенное поведение. Ваш класс Base не имеет виртуального деструктора, что означает, что delete b вызовет неопределенное поведение.

Причины UB при вызове delete варьируются от факта, что он не будет освобождать производных ресурсов (что, похоже, является целью кода, ой!), До факта что он попытается освободить выделенную память, которая может работать или нет, в зависимости от расположения обоих объектов. Если ему не удастся освободить память, он, вероятно, потерпит крах, если ему удастся разместить новый вызов в следующей строке, он попытается инициализировать объект в памяти, который уже был освобожден ...

Даже если вы изменили код (пытаясь избежать проблем с освобождением) на:

Base* Base::create(int i) {
   Derived *d = new Derived;
   Base * b = static_cast<Base*>(d);
   b->~Base();                       // destruct, do not deallocate
   new (b) Base(i);
   return d;
}

Там, где delete нет и, таким образом, этот конкретный источник неопределенного поведения пропал, код по-прежнему остается неопределенным поведением (вероятно, слишком многими способами, чтобы даже упоминать). Поскольку вызов деструктора по-прежнему является UB, даже если это не так, тот факт, что вы воссоздали тип Base, означает, что динамическая диспетчеризация для этого объекта, вероятно, будет считать объект Base, а не * 1018. * объект (в случае vtable с vptr, который указывает на информацию типа RunTime, будет ссылаться на Base, а не Derived)

И, вероятно, есть две или три другие вещи, которые могут пойти не так, и я не могу думать прямо сейчас ...

1 голос
/ 03 ноября 2011

delete b не просто вызывает деструктор Base, но также освобождает память, возвращаемую new, и вызывает деструктор Derived [предполагая, что вы намеревались Base иметь виртуальный деструктор ... если вы намеревался, чтобы это было не виртуальным, поведение просто неопределено для начала]. Это означает, что ваше последующее использование размещения new создает в памяти новый объект Base, который больше не действителен, и вы никогда не замените часть Derived, которую вы уничтожили ранее. Короче говоря, ничто из того, что вы сделали, даже не подходит к правильному поведению.

Честно говоря, я не понимаю, что вы пытаетесь сделать ... почему Derived должен создаваться по умолчанию, а не просто пересылать аргумент? Это не глупо, это то, как все делается.

...