Почему у нас нет виртуального конструктора в C ++? - PullRequest
228 голосов
/ 09 апреля 2009

Почему в C ++ нет виртуального конструктора?

Ответы [ 20 ]

218 голосов
/ 09 апреля 2009

Услышь это изо рта лошади :).

Из часто задаваемых вопросов по стилю и технике C ++ Бьярна Страуструпа Почему у нас нет виртуальных конструкторов?

Виртуальный вызов - это механизм для выполнения работы при частичной Информация. В частности, «виртуальный» позволяет нам вызывать функцию зная только любые интерфейсы, а не точный тип объекта. к создать объект вам нужна полная информация. В частности, вы нужно знать точный тип того, что вы хотите создать. Как следствие, «вызов конструктора» не может быть виртуальным.

Запись FAQ часто дает код для способа достижения этой цели без виртуального конструктора.

121 голосов
/ 09 апреля 2009

Виртуальные функции в основном обеспечивают полиморфное поведение. То есть, когда вы работаете с объектом, динамический тип которого отличается от статического (время компиляции) типа, к которому он относится, он обеспечивает поведение, соответствующее типу объекта фактический вместо статический тип объекта.

Теперь попробуйте применить такое поведение к конструктору. При создании объекта статический тип всегда совпадает с фактическим типом объекта, поскольку:

Чтобы создать объект, конструктору нужен точный тип объекта, который он должен создать [...] Кроме того, [...] вы не можете иметь указатель на конструктор

(Бьярне Страуступ (P424 Язык программирования C ++ SE))

57 голосов
/ 09 апреля 2009

В отличие от объектно-ориентированных языков, таких как Smalltalk или Python, где конструктор представляет собой виртуальный метод объекта, представляющего класс (это означает, что вам не нужен GoF абстрактный шаблон фабрики , как вы можете передать объект, представляющий класс вокруг вместо создания собственного), C ++ является языком, основанным на классе, и не имеет объектов, представляющих какие-либо конструкции языка. Класс не существует как объект во время выполнения, поэтому вы не можете вызывать виртуальный метод для него.

Это соответствует философии «ты не платишь за то, что не используешь», хотя каждый крупный проект C ++, который я видел, заканчивал тем, что реализовывал некоторую форму абстрактной фабрики или отражения.

39 голосов
/ 10 апреля 2009

две причины, которые я могу придумать:

Техническая причина

Объект существует только после завершения работы конструктора. Для того чтобы конструктор отправлялся с помощью виртуальной таблицы, должен существовать объект с указателем на виртуальную таблицу, но как может существовать указатель на виртуальную таблицу если объект все еще не существует? :)

Логическая причина

Вы используете ключевое слово virtual, когда хотите объявить несколько полиморфное поведение. Но в конструкторах нет ничего полиморфного, задача конструкторов в C ++ - просто помещать объектные данные в память. Поскольку виртуальные таблицы (и полиморфизм в целом) имеют дело с полиморфным поведением, а не с полиморфными данными, объявлять виртуальный конструктор бессмысленно.

13 голосов
/ 09 апреля 2009

Да, просто не конструктор: -)

struct A {
  virtual ~A() {}
  virtual A * Clone() { return new A; }
};

struct B : public A {
  virtual A * Clone() { return new B; }
};

int main() {

   A * a1 = new B;
   A * a2 = a1->Clone();    // virtual construction
   delete a2;
   delete a1;
}
13 голосов
/ 23 ноября 2009

За исключением семантических причин, vtable не существует до тех пор, пока объект не будет построен, что делает виртуальное обозначение бесполезным.

12 голосов
/ 24 сентября 2015

Сводка : стандарт C ++ может определять нотацию и поведение для "виртуальных конструкторов", которые достаточно интуитивны и не слишком сложны для поддержки компиляторами, но зачем вносить Стандартные изменения именно для этого, если функциональность уже может быть реализована с использованием create() / clone() (см. ниже)? Это не так полезно, как многие другие языковые предложения в процессе разработки.

Обсуждение

Давайте постулируем механизм «виртуального конструктора»:

Base* p = new Derived(...);
Base* p2 = new p->Base();  // possible syntax???

В приведенном выше примере первая строка создает объект Derived, поэтому таблица виртуальной диспетчеризации *p может разумно предоставить «виртуальный конструктор» для использования во второй строке. (Десятки ответов на этой странице о том, что «объект еще не существует, поэтому виртуальное строительство невозможно» излишне близоруко сфокусированы на объекте, который будет построен.)

Во второй строке постулируется нотация new p->Base() для запроса динамического выделения и построения по умолчанию другого Derived объекта.

Примечания:

  • компилятор должен организовать распределение памяти перед вызовом конструктора - конструкторы обычно поддерживают автоматическое (неофициально "стек") выделение, статические (для глобальной области / области имен и объектов класса / функции- static) и динамические (неофициально "куча") при использовании new

    • размер объекта, который должен быть построен с помощью p->Base(), вообще не может быть известен во время компиляции, поэтому динамическое распределение - единственный подход, который имеет смысл

  • для динамического выделения он должен вернуть указатель, чтобы память могла быть delete d позже.

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

Компилятору потребуется:

  • выясните, сколько памяти Derived необходимо, либо вызвав неявную функцию virtual sizeof, либо получив такую ​​информацию через RTTI
  • вызов operator new(size_t) для выделения памяти
  • invoke Derived() с размещением new.

OR

  • создать дополнительную запись vtable для функции, которая сочетает в себе динамическое размещение и конструкцию

Итак - не кажется непреодолимым указывать и реализовывать виртуальные конструкторы, но вопрос на миллион долларов: как это будет лучше, чем то, что возможно при использовании существующих возможностей языка C ++ ...? Лично, Я не вижу никакой выгоды по сравнению с решением ниже.


`clone ()` и `create ()`

FAQ по C ++ документирует идиому «виртуального конструктора» , содержащую методы virtual create() и clone() для создания по умолчанию или копирования-создания нового динамически размещаемого объекта:

class Shape {
  public:
    virtual ~Shape() { } // A virtual destructor
    virtual void draw() = 0; // A pure virtual function
    virtual void move() = 0;
    // ...
    virtual Shape* clone() const = 0; // Uses the copy constructor
    virtual Shape* create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
  public:
    Circle* clone() const; // Covariant Return Types; see below
    Circle* create() const; // Covariant Return Types; see below
    // ...
};
Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }

Также возможно изменить или перегрузить create() для приема аргументов, хотя для соответствия сигнатуре функции virtual базового класса / интерфейса аргументы для переопределений должны точно соответствовать одной из перегрузок базового класса. С помощью этих явных пользовательских средств легко добавить ведение журнала, инструментарий, изменить распределение памяти и т. Д.

5 голосов
/ 21 июля 2015

Виртуальные функции в C ++ являются реализацией полиморфизма во время выполнения, и они будут выполнять переопределение функций. Обычно ключевое слово virtual используется в C ++, когда вам нужно динамическое поведение. Это будет работать только тогда, когда объект существует. Тогда как конструкторы используются для создания объектов. Конструкторы будут вызываться во время создания объекта.

Таким образом, если вы создаете конструктор как virtual, согласно определению виртуального ключевого слова, у него должен быть существующий объект для использования, но конструктор используется для создания объекта, поэтому этот случай никогда не будет существовать. Поэтому вы не должны использовать конструктор как виртуальный.

Итак, если мы попытаемся объявить компилятор виртуального конструктора, выдаст ошибку:

Конструкторы не могут быть объявлены виртуальными

5 голосов
/ 17 октября 2011

Хотя концепция виртуальных конструкторов не очень подходит, поскольку тип объекта является обязательным условием для создания объекта, он не полностью переопределен.

В шаблоне проектирования «фабричного метода» GOF используется «концепция» виртуального конструктора, которая удобна в определенных ситуациях проектирования.

5 голосов
/ 04 мая 2018

Вы можете найти пример и техническую причину, почему это не разрешено в ответе @stefan. Теперь логичный ответ на этот вопрос по мне:

Основное использование виртуального ключевого слова - включить полиморфное поведение, когда мы не знаем, на какой тип объекта будет указывать указатель базового класса.

Но подумайте об этом более примитивном способе, для использования виртуальной функциональности вам потребуется указатель. А что требует указатель? Объект для указания! (с учетом случая правильного выполнения программы)

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

Теперь представьте себе ситуацию с моментом, когда объекту, на который нужно указать класс, присваивается некоторая память -> Его конструктор будет вызываться автоматически в самом экземпляре!

Таким образом, мы можем видеть, что нам на самом деле не нужно беспокоиться о том, что конструктор является виртуальным, потому что в любом случае вы хотите использовать полиморфное поведение, наш конструктор уже был бы выполнен, делая наш объект готовым к использованию!

...