2008-08-31 1 views
12

В 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 пока они решили пойти другим путем, было единственной причиной, принцип наименьшего удивления?

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

ответ

11

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

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

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

7

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

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

1

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

С помощью подхода .NET подход должен быть очень ограниченным, чтобы не полагаться на какое-либо производное состояние объекта.

+1

Как это больше подходит для компилятора? Он просто сводится к установке vptr после вызова конструктора базового класса. Я бы сказал, что другие семантики сложнее реализовать, так как вам нужно убедиться, что после установки vptr в конструкторе производного класса он не должен быть переоценен конструкторами базовых классов. (Предполагается, что динамическая диспетчеризация обрабатывается с помощью указателей на таблицы виртуальных методов, что является наиболее распространенным подходом.) – 2012-05-25 16:17:20

2

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

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

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

0

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; 
0

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

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

Затем снова преимущество C++ поведения является то, что он отбивает constuctors как выше от писаться.

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

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^