Виртуальные функции в конструкторах, почему языки отличаются? - PullRequest
12 голосов
/ 31 августа 2008

В C ++, когда виртуальная функция вызывается из конструктора, она не ведет себя как виртуальная функция.

Я думаю, что все, кто сталкивался с таким поведением в первый раз, были удивлены, но с другой стороны, это имело смысл:

Пока производный конструктор не был выполнен, объект является не и все же производным экземпляром.

Так как можно вызвать производную функцию? Предварительные условия не имели возможности быть настроенными. Пример:

class base {
public:
    base()
    {
        std::cout << "foo is " << foo() << std::endl;
    }
    virtual int foo() { return 42; }
};

class derived : public base {
    int* ptr_;
public:
    derived(int i) : ptr_(new int(i*i)) { }
    // The following cannot be called before derived::derived due to how C++ behaves, 
    // if it was possible... Kaboom!
    virtual int foo()   { return *ptr_; } 
};

Это точно так же для Java и .NET, но они решили пойти другим путем, и, возможно, единственная причина для принципа наименьшего удивления ?

Какой, по вашему мнению, правильный выбор?

Ответы [ 6 ]

11 голосов
/ 16 сентября 2008

Существует фундаментальное различие в том, как языки определяют время жизни объекта. В Java и .Net члены объекта инициализируются нулями / нулями до запуска любого конструктора, и в этот момент начинается время жизни объекта. Поэтому, когда вы входите в конструктор, вы уже получили инициализированный объект.

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

Проблема с определением времени жизни объекта в Java / .Net заключается в том, что труднее убедиться, что объект всегда соответствует своему инварианту, без необходимости указывать специальные случаи, когда объект инициализируется, но конструктор не запускается. Проблема с определением C ++ состоит в том, что у вас есть этот странный период, когда объект находится в подвешенном состоянии и не полностью построен.

7 голосов
/ 31 августа 2008

Оба способа могут привести к неожиданным результатам. Лучше всего вообще не вызывать виртуальную функцию в конструкторе.

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

2 голосов
/ 01 сентября 2008

Виртуальные функции в конструкторах, почему языки отличаются?

Потому что нет ни одного хорошего поведения. Я считаю, что поведение C ++ имеет больше смысла (поскольку c-tors базового класса вызывается первым, вполне понятно, что они должны вызывать виртуальные функции базового класса - в конце концов, производный класс c-tor еще не запущен, может не установить правильные предварительные условия для виртуальной функции производного класса).

Но иногда, когда я хочу использовать виртуальные функции для инициализации состояния (поэтому не имеет значения, что они вызываются с неинициализированным состоянием), поведение C # / Java лучше.

1 голос
/ 31 августа 2008

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

При подходе .NET функция должна быть очень ограниченной, чтобы не полагаться ни на какое производное состояние объекта.

0 голосов
/ 17 сентября 2008

Я нахожу поведение C ++ очень раздражающим. Вы не можете написать виртуальные функции, например, чтобы вернуть желаемый размер объекта, и чтобы конструктор по умолчанию инициализировал каждый элемент. Например, было бы неплохо сделать:

BaseClass() { for (int i=0; i<virtualSize(); i++) initialize_stuff_for_index(i); }

С другой стороны, преимущество поведения C ++ состоит в том, что оно препятствует написанию конструкторов, подобных приведенным выше.

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

Еще одно замечание по отношению к C ++ состоит в том, что поведение намного менее эффективно. Хотя конструктор точно знает, что он вызывает, указатель vtab нужно менять для каждого отдельного класса от базового до конечного, поскольку конструктор может вызывать другие методы, которые будут вызывать виртуальные функции. Исходя из моего опыта, это тратит гораздо больше времени, чем экономится, делая вызовы виртуальных функций в конструкторе более эффективными.

Гораздо более раздражающим является то, что это также относится к деструкторам. Если вы пишете виртуальную функцию cleanup (), а деструктор базового класса выполняет cleanup (), она, безусловно, не выполняет то, что вы ожидаете.

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

0 голосов
/ 16 сентября 2008

Delphi хорошо использует виртуальные конструкторы в среде VCL GUI:

type
  TComponent = class
  public
    constructor Create(AOwner: TComponent); virtual; // virtual constructor
  end;

  TMyEdit = class(TComponent)
  public
    constructor Create(AOwner: TComponent); override; // override virtual constructor
  end;

  TMyButton = class(TComponent)
  public
    constructor Create(AOwner: TComponent); override; // override virtual constructor
  end;

  TComponentClass = class of TComponent;

function CreateAComponent(ComponentClass: TComponentClass; AOwner: TComponent): TComponent;
begin
  Result := ComponentClass.Create(AOwner);
end;

var
  MyEdit: TMyEdit;
  MyButton: TMyButton;
begin
  MyEdit := CreateAComponent(TMyEdit, Form) as TMyEdit;
  MyButton := CreateAComponent(TMyButton, Form) as TMyButton;
end;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...