2015-05-01 2 views
0

Я хочу знать, является ли один из следующих двух вариантов дизайна дизайна превосходным с точки зрения производительности и/или ремонтопригодности или других проблем, которые будут способствовать одному подходу к другому.C++ Class Inheritance Design Choice

Первый подход:

Базовый класс (родитель) имеет частный элемент данных и одну чистую виртуальную функцию пустот. Детский класс (childA) передает параметр из его конструктора по умолчанию родительскому и использует метод getter для родителя, чтобы распечатать другое значение.

Второй подход:

Базовый класс (Oncle) состоит только из деструктора в Virual и чистого виртуального метода. В дочернем классе (племяннике) хранится значение как частный элемент данных и имеет собственную функцию getter для выполнения некоторых вычислений.

Вот код:

#include <iostream> 

class parent{ 
public: 
    parent(int x){ _x = x; } 
    virtual ~parent(){} 
    virtual void calc(void) = 0; 
    int getX(void) { return _x; } 
private: 
    int _x; 
}; 

class childA: public parent{ 
public: 
    childA(int x): parent(x){} 
    void calc(void){std::cout << "x sq: " << parent::getX()*parent::getX() << std::endl;} 
}; 


class oncle{ 
public: 
    virtual ~oncle(){} 
    virtual void calc(void) = 0; 
}; 

class nephewA: public oncle{ 
public: 
    nephewA(int x): _x(x){} 
    void calc(void){std::cout << "x sq: " << getX()*getX() << std::endl;} 
    int getX(void){ return _x; } 
private: 
    int _x; 
}; 

int main(int argc, char *argv[]) 
{ 

    parent *first = new childA(3); 
    parent *second = new childB(3); 

    first->calc(); 
    second->calc(); 

    oncle *foo = new nephewA(3); 
    oncle *bar = new nephewB(3); 
    foo->calc(); 
    bar->calc(); 


    delete bar; 
    delete foo; 
    delete second; 
    delete first; 

    return 0; 
} 

Короче говоря: первый подход делает акцент на родителей, а второй на дочернем классе.

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

+3

Приложение должно определить, какой подход необходим. – NathanOliver

+2

Возможный переход на [email protected] –

+0

Вопрос, который будет просто дискуссией. На самом деле не должно быть в этом разделе. – Khaldor

ответ

1

Производительность:

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

Тем не менее, для этого простого примера, очевидно, что производительность обоих подходов должна быть таким же:

  • Подход 1: каждый вызов calc() виртуальный вызов (вызов косвенной функции). Функция вызывает дважды родительский getX(), который является вызовом не виртуальной функции (т. Е. Каждый calc() знает во время компиляции, какую функцию он должен вызывать) полностью определяется во время компиляции.
  • Подход 2: каждый звонок до calc() также является виртуальным вызовом (непрямой вызов функции). Функция вызывает дважды объект getX(), который является вызовом не виртуальной функции, полностью определяемым во время компиляции.

Кроме того, компилятор может легко вводить здесь не виртуальные функции. Так что оптимизированный код, конечно, даже не вызовет функцию, а напрямую использует переменную.

Maintenability

Вы должны спросить себя, насколько члены (х/GetX()) на самом деле связаны с классом, в котором они определены, и как вероятно, это то, что каждый элемент дизайна может развиваться.

  • Подход 1 предполагает, что х управляются одинаково для всех детей без исключения.
    • Если это так, эта конструкция имеет преимущество центрального обслуживания. Например, если завтра вам понадобится x, чтобы стать двойным, было бы прямолинейно реализовать такую ​​эволюцию.
    • Если это базовое предположение изменилось, все, что вам нужно сделать, это определить getX() для детей, которые должны иметь другое поведение. Единственным недостатком было бы то, что у этих детей все равно останется частный х, который останется неиспользованным. Если у вас есть несколько объектов, это не является серьезной проблемой. Если бы у вас были миллионы таких объектов в памяти, это могло бы оказаться неоптимальным.
  • подхода 2 предполагает, что каждый племянник отвечает за управление своим собственной getX() Это очень гибкий дизайн, особенно, если есть много различных правил:
    • Управление памяти будет всегда быть адаптированы к каждому племяннику. Если бы один племянник, например, вывел x из другого значения, не было бы необходимости хранить избыточную информацию.
    • Гибкость также может иметь свой недостаток, особенно если она не соответствует действительности: может потребоваться скопировать и вставить одно и то же определение одних и тех же членов по многим племянникам. Такой код тогда будет трудно поддерживать (потому что любой chagne для x или getX() должен быть реплицирован по всем племянникам).

Выводы:

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

Некоторые примеры:

  • если родитель является работником, а ребенок его роль в рабочем процессе, а х возраст, я бы пойти на подходе 1 (х действительно связаны с родителем).
  • if oncle - абстрактная геометрическая форма, newphew - конкретная форма, такая как квадрат/треугольник/октогон, а x - максимальная ширина, я бы пошел на подход 2 (x действительно связан с племянником).
+0

Спасибо за подробный ответ. – Vincent