2012-02-17 4 views
0

Как я могу вычислить значение переменной-члена дочернего класса в его конструкторе, а затем перейти к конструктору родителя?Как дочерний класс вызывает конструктор родительского класса, который по-разному инициализирует переменные-члены? [C++]

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

Например:

Car.h

class Car 
{ 
public: 
    Car(); 
    Car(double Price) ; 
    ... 
private: 
    double price; 
    double DetermineMarketPrice(); 

}; 

Car.cpp

Car::Car() 
{ 
    //some other long computation 
    price = DetermineMarketPrice(); 
} 
Car::Car(double Price) 
{ 
    price = Price; 
} 
... 

Porche.h

class Porche : public Car 
{ 
    public: 
     Porche(); 
    ... 
    private: 
     double price; 
     double discount; 
     double fee; 
     double DetermineMarketPrice(); 
     double RetrieveFee(); 
     double CheckDiscount(); 
     ... 
}; 

Porche.cpp

Porche::Porche():Car(price) 
{ 
    discount = CheckDiscount(); 
    fee = = RetrieveFee(); 
    price = DetermineMarketPrice() * (1-discount) + fee; 
} 

В этом случае цена Porche еще не известна. Он должен быть вычислен в конструкторе. Если я вызову конструктор родителя, как это, похоже, он будет пропускать еще не инициализированную цену.

Что было бы хорошим способом передать некоторое значение переменных-членов, которые могут быть известны только в конце инициализации класса Child?

+0

Можете ли вы определить значение «Определить маркеры», «RetrieveFee» и «CheckDiscout»? – fdlm

+0

В любом случае, «double» не подходит для денег. Значение 'lots_of_pennies * 0.01' не обязательно равно' lots_of_pennies/100'. –

+0

Спасибо за ваши комментарии и советы! Теперь я понял, что вместо того, чтобы рассматривать это как техническую проблему, как в том, как ее кодировать, я должен рассматривать это скорее как проблему дизайна. Хотя есть способы обойти его, я думаю, что производные классы должны просто быть родительским классом с дополнительными спецификациями или функциями. Наследование не предназначено для того, чтобы рассматривать два класса, делающих разные вещи как один класс. Я предполагаю, что я бы сделал, это создать еще один общий класс, который будет служить родителем для обоих этих классов. – tuzzer

ответ

4

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

EDIT: Существует технически способ обойти эту проблему, создав второй конструктор или имеющий конструктор по умолчанию со значением по умолчанию, который может использоваться для остановки вычислений в базовом классе, например:

struct SkipCalculatePrice {}; 

class Car { 
public: 
    Car(); 
protected: 
    Car(SkipCalculatePrice); 
}; 

class Ferrari: public Car { 
public: 
    Ferrari(): Car(SkipCaluclatePrice()) [...] 
[...] 

Однако, я лично не рекомендовал бы это как хорошую конструкторскую практику. Понятно, что один хотел бы избежать задержки инициализации в качестве анти-паттерна, однако, для дорогостоящих расчетов, правильно сделали ленивым initailization может быть правильный ответ:

class Car { 
    virtual double calculatePrice(); 
    bool priceCalculated; 
    double price; 
public: 
    double getPrice() { 
     if(!priceCaluclated) { 
      price = calculatePrice(); 
     } 
     return price; 
    } 
} 

class Ferrari: public Car { 
    double calculatePrice(); 
}; 
+0

Зачем переносить дорогостоящие вычисления из конструктора? – fdlm

+0

@fdlm: [Этот вопрос] (http://stackoverflow.com/questions/293967/) обсуждает эту проблему в полном объеме. –

+0

В этом случае есть и прагматический, и философский ответ на вопрос. С прагматичной точки зрения базовый конструктор называется первым этапом построения производного класса, поэтому расчеты там, которые являются дорогостоящими и ненужными, являются расточительными. Из философского дизайна POV класс Car представляет то, что делает автомобиль автомобилем, а конструктор настраивает вещи, общие для всех автомобилей. Если Ferrari - это автомобиль, а цена, рассчитанная классом Car, не относится к Ferrari, то это не то, что характерно для всех автомобилей. – AndrzejJ

1

Просто переместите код вычисления из конструктора в функцию полезности, такую ​​как CalculatePrice(). Постройте объект, а затем вызовите эту функцию.

+0

Но тогда я знаю, что мне нужно будет рассчитать цену каждый раз, когда я «делаю» автомобиль. Если программист забыл использовать функцию CalculatePrice() сразу после создания объекта, тогда цена не будет инициализирована и может привести к некоторым другим ошибкам. Вот почему я попытался сделать все, что было у конструктора, так как это должно быть сделано в любом случае. – tuzzer

+0

В этом случае создайте фабрику, которая и конструирует транспортные средства, и вызывает функцию расчета. – Nick

-1

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

+0

Вызов виртуального метода в конструкторе не будет работать так, потому что дочерний объект еще не был создан при вызове этого метода. –

+0

Это не будет работать, когда вы вызываете виртуальную функцию из конструктора, будет вызвана функция базового класса, а не производная. – AndrzejJ

+0

О да, я забыл ... Мой разум сейчас в режиме java. – UmNyobe

5

Многое зависит от реальной ситуации, но один частое решением является разгрузки всех расчетов в статический член, так что вы можете написать:

Porsche::Porsche() 
    : Car(calclulatePrice()) 
{ 
    // ... 
} 

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

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

+0

Переменная 'price' может быть защищена или манипулировать функциями в базовом классе. Скажите 'CalculatePrice()' не может быть статическим. Вероятно, лучше, если он не вызывается в конструкторе. Но так как я знаю, что 'CalculatePrice()' должен быть выполнен, я думал, что было бы целесообразно вызвать эту функцию в конструкторе. Однако, поскольку способ вычисления цены отличается для дочернего класса, я не хочу использовать конструктор базового класса для использования реализации 'CalculatePrice()' базового класса. Вместо этого конструктор должен использовать версию класса Child. – tuzzer

+0

Если я сказал, что перегрузил «CalculatePrice()» в классе Porche (child), когда конструктор класса Car (parent) вызывает «CalculatePrice()», я считаю, что он назвал бы Car's one. – tuzzer

+0

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

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