Отправляет email-рассылки с помощью сервиса Sendsay

C++ для всех

  Все выпуски  

C++ для всех


Информационный Канал Subscribe.Ru

C++ для всех. Выпуск 6

Друзья класса. Вложенные классы.

Здравствуйте, уважаемые подписчики

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

Друзья класса

Как Вы уже знаете, в классе существует механизм управления доступом, который продуцирует три области доступа - открытую (public), защищенную (protected) и закрытую (private). К членам класса открытой области доступ не ограничен, к членам защищенной имеют доступ только производные классы, а к членам закрытой области могут получить доступ только функции-члены данного класса. Все так и было, пока мы не знали о спецификаторе friend. Данный спецификатор открывает объявленному классу или функции полный доступ ко всем членам данного класса. Принято записывать friend-объявления сверху в теле класса, однако место объявления роли не играет - главное объявить в теле класса. Рассмотрим пример объявления и использования классов-друзей:


 class B;

 class A
 { 
     friend class B;
 protected:
  void func_protected(){}
 private:
  void func_private(){} 
 public:
  void func_public(){}
 };

 class B
 { 
 public:
  void func_any(){
   A a;
   //без friend-объявления нижеприведенные функции будут недоступны
   a.func_protected();
   a.func_private();
  }
 };

Обратите внимание на первую строку примера: здесь мы объявляем класс B, иначе будет ошибка компиляции с сообщением типа "класс B неизвестен".

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


 class B
 {
 public:
  void printValue(); //реализация доступа в A::value в *.cpp файле
 };

 class A
 {
  friend void printValue();
  friend void B::printValue();
 public:
  A(int v=0):value(v){}
 private:
  int value;
 }; 
 
 void printValue()
 {
  A a(100);
  a.value = 400;
 }
 
 int main()
 {
  B b;
  b.printValue();
  
  printValue();

  return 0;
 }

friend-объявления распространяются только на конкретный объявленный класс, т.е. если необходим полный доступ к членам класса из наследников frind-класса, то необходимо объявлять друзьями всех необходимых наследников. Подобное не пройдет:


 class B;

 class A
 {
  friend class B;
 private:
  void func_private(){}
 };
 
 class B
 {
 public:
  //нормально
  void exec_A_private(){ A a; a.func_private(); }
 };

 class BX : public B
 {
 public:
  //уже не проходит
  void exec_A_private(){ A a; a.func_private(); }
 };

Нужно так:


 class B;
 class BX

 class A
 {
  friend class B;
  friend class BX;
 private:
  void func_private(){}
 };
 
 class B
 {
 public:
  //нормально
  void exec_A_private(){ A a; a.func_private(); }
 };

 class BX : public B
 {
 public:
  //нормально
  void exec_A_private(){ A a; a.func_private(); }
 };

С другой стороны, при наследовании класса, в котором объявлены дружественные функции или классы, действие объявления распространяется на всех наследников.

 
 class A
 {
  friend class B;
 private:
  void func_private(){}
 };

 class AX : public A {};
 class AXX : public AX {};

 class B
 {
 public:
  void exec_AXX_private(){ AXX a; a.func_private(); } 
 };

Теперь о применении.

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

Вложенные классы

Вложенные классы - это обычные классы, члены которых имеют полный доступ ко всем членам родителя (слово 'родитель' следует понимать не в плане наследования, а в плане непосредственной принадлежности). Объявляются вложенные классы внутри тела родителя, при этом важна область видимости, в которой класс был объявлен. Через объект родительского класса доступ к членам вложенного класса возможен (при наявности допустимых прав) через оператор ::. Смотрим пример:


 class AMain
 {
 public:
  struct A
  {
   void printValue();
  };
  const A& getA();

 protected:
  struct B
  {
   void printValue();
  };
  const B& getB();

 private:
  class C
  {
   void printValue();
  };
  const C& getC();

  A a;
  B b;
  C c;
 };
 void AMain::A::printValue()
 {
 }
 void AMain::B::printValue()
 {
 }
 void AMain::C::printValue()
 {
 }

 const AMain::A& AMain::getA()
 {
  return a;
 }
 const AMain::B& AMain::getB()
 {
  return b;
 }
 const AMain::C& AMain::getC()
 {
  return c;
 }
 
 int main()
 {
  AMain a;
  const AMain::A &aa = a.getA();
  AMain::A aa1;
  const AMain::B bb; //ошибка компиляции - данная структура в protected-области
  
  return 0;
 }

Т.к. вложенные классы не отличаются от обыкновенных невложенных, допустимо из обычное наследование и т.п.:


 class A
 {
 protected:
  class AIn
  {
  public:
   void print(){ printValue(); }
  protected:
   virtual void printValue(){ printf("A::AIn\n");  }
  };
 };
 
 class B : public A
 {
  public:
  void print(){ b.print(); }
 
 protected:
  class BIn : public AIn
  {
  protected:
   virtual void printValue(){ printf("B::BIn\n");  }
      };

 private:
  BIn b;
 };

Главное преимущество вложенных классов заключается в том, что не засоряется глобальная область видимости - если необходима структура или класс для внутреннего хранения данных, то лучшего, чем вложенный класс трудно придумать. В связи с этим и главная рекомендация - вложенными делайте только те классы, которые логически тесно связаны с родительским классом. Нерекомендуемо сильно развивать иерархию вложенных классов, т.к. это ухудшает читаемость - Вы получите механизм обращения типа A::A1::A2::, а это как правило не способствует упрощению понимания логики программы.

В заключение кратко рассмотрим механизм объявления и использования вложенного enum-типа:


 class A
 {
 public:
  enum Color{ red,black,green,cian };

  void setColor(Color c);
  Color getColor()const;

 private:
  Color col;
 };

 void A::setColor(A::Color c)
 { 
  col=c; 
 }
 A::Color A::getColor()const
 {
  return col;
 }

 int main()
 {
  A a;
  a.setColor(A::red);

  A::Color color = a.getColor();
  return 0;
 }


На сегодня все. Пожелания, предложения, вопросы прошу на iqsoft@cg.ukrtel.net

В следующем выпуске: Шаблонные классы.


(с) Юрий Гордиенко



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное