Функции C ++ для абстрактного базового класса - PullRequest
2 голосов
/ 01 ноября 2009

Предположим, я хочу иметь иерархию наследования, подобную этой.

class Base

class DerivedOne : public Base

class DerivedTwo : public Base

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

Однако, есть некоторые функции, которые вы хотели бы, чтобы ваши производные классы получали из вашего базового класса. Эти функции изменяют частные элементы данных, которые будут иметь DerivedOne и DerivedTwo .

class Base {
public:
      virtual void MustDefine() =0; // Function that derived classes must define
      void UseThis(); // Function that derived classes are meant to use
};

Однако функция UseThis() предназначена для изменения личных данных членов. Вот тут и возникает вопрос. Должен ли я предоставить фиктивные закрытые члены класса Base ? Должен ли я предоставить ему защищенные элементы данных (и, следовательно, производные классы не будут объявлять свои собственные частные элементы данных). Я знаю, что второй подход уменьшит инкапсуляцию.

Каков наилучший подход к такой ситуации? Если понадобится более подробное объяснение, я с радостью его предоставлю.

Ответы [ 6 ]

5 голосов
/ 01 ноября 2009

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

2 голосов
/ 01 ноября 2009

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

В вашем случае это будет что-то вроде:

class IBlaBla;

class BlaBlaBase : public IBlaBla;

class DerivedOne : public BlaBlaBase

class DerivedTwo : public BlaBlaBase

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

Должен ли я дать манекен базового класса личные данные членов?

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

1 голос
/ 01 ноября 2009

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

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

0 голосов
/ 01 ноября 2009

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

Всякий раз, когда я поддавался искушению поместить удобную функциональность в мой абстрактный базовый класс, я впоследствии сожалел об этом (скоро!). Это отнимает много гибкости. Решение , предложенное AlexKR , несколько улучшает эту ситуацию.

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

class Interface { public: virtual void f() = 0; };

class Convenience { 
   public: 
   void tweakMyMembers( int& member1, float& member2 ); 
   bool somestate;
};

class Implementor : public Interface {
   int m1; float m2;
   public: Implementor( bool b ): conv( b ) {}

   virtual void f() { conv.tweakMyMembers( m1, m2 ); if( m1<m2 ) dothis(); }
};
0 голосов
/ 01 ноября 2009

Вот возможное решение вашей дилеммы:

class Base {
 public:
   virtual ~Base() {}

   virtual void redefine_me() = 0;
   void utility_function();

 private:
   virtual int get_data_member() = 0;
   virtual void set_data_member(int v) = 0;
};

class Derived1 : public Base {
 public:
   virtual void redefine_me() { do_d1_stuff(); }

 private:
   int my_private_idaho_;
   virtual int get_data_member() { return my_private_idaho_; }
   virtual void set_data_member(int v) { my_rpviate_idaho_ = v; }
};

class Derived2 : public Base {
 public:
   virtual void redefine_me() { do_d2_stuff(); }

 private:
   int gomer_pyle_;
   virtual int get_data_member() { return gomer_pyle_; }
   virtual void set_data_member(int v) { gomer_pyle_ = v; }
};

void Base::utility_function()
{
   set_data_member(get_data_member() + 1);
}

Самым большим недостатком является то, что теперь доступ к частному члену данных обеспечивается виртуальным вызовом функции, что не самая дешевая вещь. Он также скрыт от оптимизатора.

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

0 голосов
/ 01 ноября 2009

Вам нужно будет определить Base :: UseThis (), в теле которого вы будете использовать поля Base (которые вам также нужно будет объявить в определении класса выше). Если вам нужен только доступ к ним в UseThis, они могут быть конфиденциальными. Если DerivedOne / Two потребуется доступ к ним, вы должны сделать их защищенными.

...