Страница 1 из 2

Про области видимости и классы-функции

Добавлено: 25 авг 2005, 13:29
HKarel
Как известно С++ не поддерживает вложенные ф-ции, однако, нередко возникают ситуации когда наличие последних было бы совсем не лишним. Например, когда ф-я имеет несколько точек выхода, и перед выходом нужно выполнить некоторую последовательность действий. К счастью существуют обходные пути, которые позволяют хотя бы частично решить эту проблему. Почему частично? Чтобы это понять рассмотрим код:

Код: Выделить всё

class A
{
private:
  int f1();
public:
  int f2();
  int f3();
};

int A::f3()
{
  // Описываем локальный класс-функцию
  class TFunc
  {
    A *const a;
  public:
    TFunc( A *a_ ) : a( a_ ){}
    int operator ()()
    {
      a->f2(); // Здесь все хорошо, можем 
               // обратиться к ф-ции f2
      a->f1(); // А в этом месте возникают проблемы.
               // Компилятор пишет, что ф-я f1 недоступна
               // в области видимости класса TFunc. 
      
      return 0;
    } 
  } func( this );
  ...
  return func();
  ...	
  return func();
}
Собственно, вопрос заключается в том, как разрешить проблему видимости, при условии, что f1 должна оставаться private, либо protected ?

Добавлено: 25 авг 2005, 14:19
Kolinus
посмотри на друзей (friend)
если класс сделать врендом другого то насколько помню он может дергать защищенные функции

Добавлено: 25 авг 2005, 14:28
HKarel
Это первое, что пришло мне в голову, вопрос в том куда поместить директиву

Код: Выделить всё

friend class TFunc;

Добавлено: 25 авг 2005, 14:42
WinMain
TFunc нужно описать не внутри метода, а внутри описания самого класса, а потом объявить его другом этого класса.

Код: Выделить всё

class A 
{ 
private: 
  int f1();
public: 
  int f2();
  int f3(); 

  class TFunc 
  { 
    A *const a; 
  public: 
    TFunc( A *a_ ) : a( a_ ){} 
    int operator ()() 
    { 
      a->f2(); 
      a->f1();       
      return 0; 
    } 
  }; 

  friend TFunc;
}; 

int A::f3() 
{ 
  TFunc func( this );
  //... 
  return func(); 
  //...    
  return func(); 
} 

Добавлено: 25 авг 2005, 15:17
HKarel
В принципе это решение, но оно имеет существенные минусы:
1. Загромождение хедер файла лишним описанием, тем более что TFunc используется только в одной ф-ции A::f3().
2. А если у меня появится еще ф-я f4, а потом и f5 (так и есть на самом деле) и каждая должна использовать свои TFunc, то загромождение хедера будет очень сильным + для каждой ф-ции придется придумывать свое имя, либо определять псевдовходящие параметры для того чтобы обеспечить перезагрузку, бр-р-р ...
В таком случае будет проше описать обычные ф-ции в общей области видимости, сделать их друзьями для класса А, что опять же не очень эффективно, т.к. использоваться они будут 2-3 раза и только для конкретных ф-ций класса А. В таком случае уж лучше goto использовать, хотя изначально от этого и хотелось уйти.

Добавлено: 25 авг 2005, 16:02
WinMain
1. Саму реализацию методов TFunc необязательно описывать в заголовочном файле, там лучше оставить только объявления его методов. А в файле .срр уже их реализовывать.
2. Вместо оператора goto внутри процедуры можно применить одиночный цикл, а оператор break выводит процедуру из тела цикла, например:

Код: Выделить всё

int MyProc()
{
 // код по инициализации каких-то параметров...
 // ....
   do {
   // основной код процедуры...
     if (func1() == FALSE)
        {
         // обработка ошибки...
         break;
        }
     if (func2() == FALSE)
        {
         // обработка ошибки...
         break;
        }
    // ........

   } while(0);
 // ....
 // код по завершению процедуры...
 return 0;
}

Добавлено: 25 авг 2005, 16:41
HKarel
Ваш первый пункт натолкнул на некоторые сумасбродные мысли и даже заставил сесть за компилятор :)
Если бы можно было делать так, то было бы вполне приемлемо

Код: Выделить всё

class A
{
  int f1(){ return 0; }
  friend class TFunc;
  class TFunc
  {
    A *const a;
  public:
    TFunc( A *a_ ) : a( a_ ){}
    int operator()();
  };

public:
  int f2(){ return 0; }
  int f3();
  int f4();

};
//---------------------------------------------------------------------------

A::f3()
{
  int TFunc::operator()()
  {
    // реализация специфичная для ф-ции f3
  }
  TFunc func( this );

}
//---------------------------------------------------------------------------

A::f4()
{
  int TFunc::operator()()
  {
    // реализация специфичная для ф-ции f4
  }
  TFunc func( this );
}
Однако все оказалось гораздо прозаичнее, компилятор (борландовый) допускает только конструкции вида:

Код: Выделить всё

class A
{
  int f1(){ return 0; }
  friend class TFunc;
  class TFunc
  {
    A *const a;
  public:
    TFunc( A *a_ ) : a( a_ ){}
    int operator()();
  };
public:
  int f2(){ return 0; }
  int f3();
  int f4();

};
//---------------------------------------------------------------------------

int A::TFunc::operator()()
{
  ...
}
//---------------------------------------------------------------------------

A::f3()
{
  TFunc func( this );

}
//---------------------------------------------------------------------------

A::f4()
{
  TFunc func( this );
}
А это значит, что для каждой ф-ции класса А надо будет писать свою ф-цию TFunc - чего бы не хотелось. Кстати, ребятм с RSDN удалось скомпилить исходный пример на нескольких компиляторах, включая MS VC7.1, единственно открытым остался вопрос: "Насколько компиляция исходного примера соответствует стандарту ?".

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

Добавлено: 25 авг 2005, 17:48
WinMain
Можно попробовать воспользоваться шаблонами.
В заголовке класса объявляешь некий общий шаблонный метод обработки ошибок, а его специализацию пишешь для каждого конкретного случая.
А параметром шаблона может быть код ошибки или числовой идентификатор вызывающего метода.
Т.е. вместо набора методов типа func1(), func2(), func3(), ... объявляешь в заголовке класса
template <const int ID> func();

а потом теле файла .срр пишешь их специализацию:

Код: Выделить всё

// вместо чисел можно указать мнемонические константы
template<> A::func<1>()
{
  // ...
}
template<> A::func<2>()
{
  // ...
}
template<> A::func<3>()
{
  // ...
}
А вызываться методы будут соответственно
func<1>(), func<2>(), ...

Добавлено: 25 авг 2005, 18:14
HKarel
Идея в общем случае понятна, однако с реализацией возникли проблемы, можно привести полный код примера ?

Добавлено: 26 авг 2005, 12:55
ssDev
А вы ничего не путаете случаем?
Вообщето последовательности действий которые необходимо выполнить это не сурогат, а некоторая операция и по правилам ООП (в данном случае) должна являтся методом этого или производного класса.
И тогда нет необходимости в таких сурогатах.
Я бы использовал методы (лудьше всего виртуальные), для ветвлений.
Тогда отпадает проблема доступа к защищенным методам.