Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Как передать функтор в метод ждущий указатель?


Автор: EvilsInterrupt 7.12.2012, 10:42
Есть код:
Код

typedef   bool ( *ErrorHadler_type )( const std::string & filename, int code );


class ErrorHandler
{
public:

    bool operator()( const char * errText, int errCode )
    {
        errors.push_back( ErrorInfo(errText,errCode) );
        return false;
    }

    int code() const
    {
        ErrorsList_t::const_iterator  errIter = errors.begin();
        ErrorsList_t::const_iterator  errEnd = errors.end();

        int code = 0;
        while( errIter != errEnd )
        {
            if( errIter->code > code )
            {
                code = errIter->code;
            }
            ++errIter;
        }
        return code;
    }

private:

    struct ErrorInfo
    {
        const char * text;
        int code;

        ErrorInfo( const char * errText, int errCode )
            : text( errText )
            , code( errCode )
        {
        }
    };

    typedef  std::list< ErrorInfo >   ErrorsList_t;

    ErrorsList_t  errors;
};


class Parser
{
public:
    explicit Parser( ErrorHadler_type errHandler )
        : handler( errHandler )
    {
    }

    void method1()
    {
        handler( "method1()", 3 );
    }

    void method2()
    {
        handler( "method2()", 4 );
    }

private:
    ErrorHadler_type  handler;
};


int main( int argc, char* argv[] )
{
    ErrorHandler errHandler;
    Parser parser1( errHandler );    // FAIL

    /*
    errHandler("Proba 1", 1 ); // OK
    errHandler("Proba 2", 2 ); // OK
    */

    std::cout << "Error code : " << errHandler.code() << std::endl;

    return EXIT_SUCCESS;
}


Я ожидаю что передавая объект, автоматически вызовется operator(). Мне нужно чтобы класс парсера вызвал этот оператор, а тот в свою очередь поменял состояние объекта,запомнив переданную ошибку.

Прав ли я? Или все не так просто и функтор не может менять состояние объекта? Если да, то мне похоже нужно юзать static метод, а в нем писать либо статическую переменную или обращение к синглтону или другую магию...

Автор: volatile 7.12.2012, 12:00
Цитата(EvilsInterrupt @  7.12.2012,  10:42 Найти цитируемый пост)
функтор не может менять состояние объекта?

Здесь это не причем. 
Вы передаете ему копию объекта, вот состояние этой копии он и меняет.
Здесь 2 пути. либо передавать ему ссылку на объект, либо копию объекта хранящуюся в парсере считать основной.
Выбор способа зависит от логики вашей программы (чтото здесь советовать трудно, т.к. мало информации.)
1-ый способ:
Цитата(EvilsInterrupt @  7.12.2012,  10:42 Найти цитируемый пост)
class Parser
{
public:
    explicit Parser( ErrorHadler & errHandler )
        : handler( errHandler )
    {
    }
    ...
    ErrorHadler & handler;
};


2-ой способ:
Цитата(EvilsInterrupt @  7.12.2012,  10:42 Найти цитируемый пост)
  std::cout << "Error code : " << parser1.errHandler.code() << std::endl;

Во 2-ом случае, нужно будет дать доступ к объекту Parser::errHandler, либо геттером, либо явно, переместив его в паблик.
 


Автор: volatile 7.12.2012, 12:33
Цитата(EvilsInterrupt @  7.12.2012,  10:42 Найти цитируемый пост)
typedef   bool ( *ErrorHadler_type )( const std::string & filename, int code );

Я не заметил что у вас в парсере хранится не объект, а указатель на функцию. 
Зачем?
operator(), имеет совсем другую сигнатуру.
вам нужно хранить либо указатель на сам объект ErrorHadler, либо ссылку на него. (что я и предложил в 1-ом способе)

Автор: volatile 7.12.2012, 12:51
Еще у вас опечатка
Цитата(EvilsInterrupt @  7.12.2012,  10:42 Найти цитируемый пост)
ErrorHandler


Короче вот весь текст целиком : smile 
Код

class ErrorHandler
{
public:

    bool operator()( const char * errText, int errCode )
    {
        errors.push_back( ErrorInfo(errText,errCode) );
        return false;
    }

    int code() const
    {
        ErrorsList_t::const_iterator  errIter = errors.begin();
        ErrorsList_t::const_iterator  errEnd = errors.end();

        int code = 0;
        while( errIter != errEnd )
        {
            if( errIter->code > code )
            {
                code = errIter->code;
            }
            ++errIter;
        }
        return code;
    }

private:

    struct ErrorInfo
    {
        const char * text;
        int code;

        ErrorInfo( const char * errText, int errCode )
            : text( errText )
            , code( errCode )
        {
        }
    };

    typedef  std::list< ErrorInfo >   ErrorsList_t;

    ErrorsList_t  errors;
};


class Parser
{
public:
    explicit Parser( ErrorHandler & errHandler )
        : handler( errHandler )
    {
    }

    void method1()
    {
        handler( "method1()", 3 );
    }

    void method2()
    {
        handler( "method2()", 4 );
    }

private:
    ErrorHandler &  handler;
};


int main( int argc, char* argv[] )
{
    ErrorHandler errHandler;
    Parser parser1( errHandler );    // ОК

    parser1.method1();
    std::cout << "Error code : " << errHandler.code() << std::endl;

    return EXIT_SUCCESS;
}

Автор: EvilsInterrupt 7.12.2012, 15:07
volatile
Ок, спасибо!

Цитата

Я не заметил что у вас в парсере хранится не объект, а указатель на функцию. 
Зачем?

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

Если точнее сформулировать требования к этой ситуации, то нужно уметь передавать :
1) Как указатель на обычную функцию
2) Так и внешне созданный объект класса с переопределенным operator(), который позволит удобным способом накапливать ошибки

Пока склоняюсь к шаблонному решению,на подобии того что сделано в std::for_each.

Код

template<typename Object, typename Operator>
std::for_each( ...., Operator op )
{
// more code

Автор: mes 7.12.2012, 20:03
EvilsInterrupt
boost::function в комплекте с boost::bind сделают мечты реальностью ))
для С++11 и буста не надо..все  в коробке))

Автор: EvilsInterrupt 8.12.2012, 10:55
mes
Полностью согласен по поводу буста, в своих проектах(домашних) применяю его и по поводу новых технологий в виде С++11 тоже согласен, но не всегда можно применить все что душе хочется :(

Попробую сформулировать проблему еще раз, а то кажется меня не понимают ;)

Вообщем мне надо на обычном С++03 мою проблему решить, т.к. он MSVS 2008.

На псевдокоде то что я хочу получить выглядит примерно так:

Код

typedef   bool ( *ErrorHandlerPtr_type )( const char * filename, int code );

class ErrorHandler
{
public:

    bool operator()( const char * errText, int errCode )
    {
        errors.push_back( ErrorInfo(errText,errCode) );
        return false;
    }

    int code() const
    {
        ErrorsList_t::const_iterator  errIter = errors.begin();
        ErrorsList_t::const_iterator  errEnd = errors.end();
        int code = 0;
        while( errIter != errEnd )
        {
            if( errIter->code > code )
            {
                code = errIter->code;
            }
            ++errIter;
        }
        return code;
    }

private:
    struct ErrorInfo
    {
        const char * text;
        int code;
        ErrorInfo( const char * errText, int errCode )
            : text( errText )
            , code( errCode )
        {
        }
    };
    typedef  std::list< ErrorInfo >   ErrorsList_t;
    ErrorsList_t  errors;
};


template< typename ErrorHandler_type = ErrorHandlerPtr_type >
class Parser
{
public:
    explicit Parser( ErrorHandler_type & errHandler = defErrorHandler )
        : handler( errHandler )
    {
    }

    void method1()
    {
        handler( "method1()", 3 );
    }

    void method2()
    {
        handler( "method2()", 4 );
    }

    bool defErrorHandler( const char * errTxt, int errCode )
    {
        return true;
    }
private:
    ErrorHandler_type & handler;
};


int main( int argc, char* argv[] )
{
    ErrorHandler errHandler;
    Parser parser1( errHandler );    // FAIL

    parser1.method1();
    parser1.method2();

    std::cout << "Error code : " << errHandler.code() << std::endl;
    return EXIT_SUCCESS;
}


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

Возникает вопрос:

Как имея:
1) Обычные функции подобные defErrorHandler
2) Имея класс функтор

Подавать в explicit-конструктор класса парсера как п.1 так и п.2 ? При этом текущие тулзы имеют семантику использования парсера в таком виде: Parser parser1(), т.е. обработчик по умолчанию.

Автор: volatile 8.12.2012, 18:07
EvilsInterrupt, а что запрещает буст::функшен использовать? там действительно все очень удобно.

Кроме-того у вас помимо основной проблемы, еще много "шума".

Цитата(EvilsInterrupt @  8.12.2012,  10:55 Найти цитируемый пост)
 Обычные функции подобные defErrorHandler

Цитата(EvilsInterrupt @  8.12.2012,  10:55 Найти цитируемый пост)
  bool defErrorHandler( const char * errTxt, int errCode )
    {
        return true;
    }

defErrorHandler - вовсе не обычная функция, это функция-мембер. а это 2 большие разницы.

Кроме-того, у вас функтор, используется не в СТЛ-овском смысле.
Он передается как ссылка на существующий "объект с состоянием", и не очень понятно, это требование, или просто побочный эффект.

Цитата(EvilsInterrupt @  8.12.2012,  10:55 Найти цитируемый пост)
Как имея:
1) Обычные функции подобные defErrorHandler
2) Имея класс функтор
Подавать в explicit-конструктор класса парсера как п.1 так и п.2 ?


Если обычная функция, это действительно обычная функция,
а передача функтора в виде ссылки на существующий "объект с состоянием"- требование, 
то можно сделать так:

Код

typedef bool (*func_type) (const std::string &, int);

// Обычная функция (дефолтный обработчик)
bool default_err_fun (const std::string & filename, int code) 
{
   std::cout << "Default plain function" << std::endl;
   return 0;
}

class ErrorHandler_base
{
public:
   ErrorHandler_base (func_type f = default_err_fun) : f_(f) {}
   virtual ~ ErrorHandler_base () {};

   virtual bool operator() (const std::string & errText, int errCode)
       { return f_ (errText, errCode); }
private:
   func_type f_;
};
//-------------------------------------------------------------------
class Parser
{
public:
    explicit Parser (ErrorHandler_base & h)
       : handler (h) {}

    explicit Parser (func_type f = default_err_fun)
       : def_handler (f), handler (def_handler) {}

    void method () { handler ("method1", 42); }

private:
    ErrorHandler_base def_handler;
    ErrorHandler_base & handler;
};

//-------------------------------------------------------------------
// Свой класс обработчик ошибок. (Требование : отнаследоваться от ErrorHandler_base)
class ErrorHandler : public ErrorHandler_base
{
public:
    bool operator()( const std::string & errText, int errCode )
    {
        code_ = errCode;
        std::cout << "ErrorHandler operator()" << std::endl;
        return 0;
    }

    int code () const { return code_; }
    // ...

private:
    int code_;
    // ...
};

// Своя обычная функция обработчик ошибок.
bool my_err_fun (const std::string & filename, int code)
{
   std::cout << "My plain function" << std::endl;
   return 0;
}


int main( int argc, char* argv[] )
{
    Parser parser_1;                // Создание парсера с дефолтным обработчиком
    parser_1.method ();

    Parser parser_2 (my_err_fun);   // Создание парсера со своей простой функцией-обработчиком
    parser_2.method();
    
    ErrorHandler errHandler;        
    Parser parser_3 (errHandler);   // Создание парсера со своим объектом-обработчиком,   
    parser_3.method ();

    std::cout << "--------------------\nError code : " << errHandler.code() << std::endl;
}


Читайте комменты в коде.
(если бы не вышеизложенные требования, то можно было бы проще)

Добавлено через 1 минуту и 9 секунд
Если опять не то, то постарайтесь упростить код вопроса, убрав все лишнее.
Как я уже говорил, у вас много лишнего "шума", причем не понятно, что из этого требование, а что случайный/ошибочный код.

Автор: volatile 8.12.2012, 18:29
Да, и забыл привести вывод:
Код

Default plain function
My plain function
ErrorHandler operator()
--------------------
Error code : 42


Если обычная функция, это не обычная функция smile , то есть она должна быть мембером Parser'а, то это тоже можно сделать.

Автор: EvilsInterrupt 8.12.2012, 23:03
volatile
Цитата

(если бы не вышеизложенные требования, то можно было бы проще)

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

Спасибо за внимание, ну и раз Вы уж упомянули "было бы проще". Хотелось бы Ваше проектное решение увидеть,если конечно не затруднит )

Автор: volatile 8.12.2012, 23:18
EvilsInterrupt, ну это подошло или нет?

Автор: EvilsInterrupt 8.12.2012, 23:35
volatile
Да, Спасибо! Пока не вижу ничего чтобы сказать "не подходит".
Все-таки хотелось бы увидеть решение более опытного товарища ;)

Автор: mes 8.12.2012, 23:52
Цитата(EvilsInterrupt @  8.12.2012,  09:55 Найти цитируемый пост)
Подавать в explicit-конструктор класса парсера

вот  скажите причем тут ехплицит  ? зачем отвлекаться на несущественные детали ?

Добавлено через 3 минуты и 13 секунд
Цитата(EvilsInterrupt @  8.12.2012,  09:55 Найти цитируемый пост)
но не всегда можно применить все что душе хочется 

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


Автор: mes 9.12.2012, 00:27
Цитата(EvilsInterrupt @  8.12.2012,  09:55 Найти цитируемый пост)
Как имея:
1) Обычные функции подобные defErrorHandler
2) Имея класс функтор

итак мы должны для одного и то же действия использовать полиморфизм, при этом, забегая вперед, имеются, как минимум, две возможности.. посредством виртуальных функций или "привязыванием", но для общего представления это не важно..  
также, нам нужно это сделать, чтоб для пользователя было прозрачным..  значит нужен враппер, и так как враппер долже иметь состояние, значит используем функтор.. 
итого две подзадачи.. 
1. адаптация функции/функтора 
2. сокрытие под удобным интерфейсом


Автор: mes 9.12.2012, 00:51
допустим интерфейс имеет такую структуру
Код

struct iface
{
  virtual bool do_some (const std::string&) =0;
};

тогда нам нужна пару адаптеров, один для функции другой для функтора..

Код

typedef bool(free_fn)(const std::string&); 
struct fn_adapter : iface 

   virtual bool do_some (const std::string& str)   
   { 
      return  _fn(str);   
   } 
   fn_adapter(free_fn *fn) : _fn(fn)   {   }  

   free_fn * _fn;    
}; 

iface * create_iface(free_fn * fn) {   return new fn_adapter(fn);  } 


для функтора аналогично..

Добавлено через 9 минут и 43 секунды
теперь обернем интерфейс
Код

struct func
{

  //для функции
  func(free_fn * fn ) 
  {
      _iface =  create_iface(fn);
  }
  //для функтора
  func (.. )
  {
     ..
  }

  bool operator()(const std::string& str)
  {
     return _iface->do_some(str);
  }  
  iface * _iface;
};

Автор: mes 9.12.2012, 01:07
для функтора поставил многоточие, потому что зависит от того, какой концепт вы выбирете для него.. можно, например, ограничить функторы наследованных от интерфейса, тогда в  последний нужно будет добавить виртуальную функцию клонирования.. 
либо прибегнуть к помощи шаблонов и организовать привязку..

Автор: mes 9.12.2012, 01:27
итого , самый простой способ:
 описать базовый функтор,  
   от которого отнаследовать адаптер для простой функции.
   все остальные функторы должны наследоваться от этой базы..
 базовый функтор должен уметь быть клонированым, 
   что будет использовано оберткой для успешного  копирования.. 
 
  помимо этого можно добавить старатегию хранения : обьект посредник, между оберткой и нтерфейсом, который выбирает перемещать ли интерфейс, или клонировать.. это поможет выбирать по ссылке ли передовать функтор или по значению..

Автор: EvilsInterrupt 26.12.2012, 22:52
Размышляя над моей текущей ситуацией по поводу обработки ошибок в моих проектах, которая привела к этому топику и мне кажется ее можно решить куда изящней. Поэтому поставлю задачу несколько по другому, чтобы увидеть мнение других разработчиков на С++.

Условия задачи:
  •  Нет груза наследия прошлого в виде уже имеющихся проектов.
  •  Код нужно писать исключительно на С++.
  •  Производительность кода имеет значение.

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

Мои мысли привели к тому что :
  Надо реализовывать Event-driven , т.е. вариант обработки ошибки с использованием callback-функции в качестве обработчика ошибки.
Реализовать обработчик ошибки по умолчанию, задаваемый в конструкторе класса парсера как параметр по умолчанию. Затем в коде класса парсера вызывается обработчик ошибки, который задан по умолчанию или реализован и передан пользователем в параметре конструктора. Этот обработчик возвращает статус : "продолжить работу дальше или нет?" имея подобный ответ мы либо бросаем исключение, либо продолжаем работу парсера дальше, но запоминаем информацию об ошибке.

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

Автор: baldina 27.12.2012, 00:43
EvilsInterrupt
Цитата(EvilsInterrupt @  26.12.2012,  22:52 Найти цитируемый пост)
дать возможность пользователю класса влиять на выбор стратегии обработки

диктуется объективными причинами, или это просто желание сделать в более общем виде?

Автор: EvilsInterrupt 27.12.2012, 08:48
Цитата

диктуется объективными причинами, или это просто желание сделать в более общем виде?

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

Автор: mes 27.12.2012, 15:34
Цитата(EvilsInterrupt @  26.12.2012,  21:52 Найти цитируемый пост)
 Надо реализовывать Event-driven

это если простой вариант с возвращением статуса окончания операции не подходит.

Цитата(EvilsInterrupt @  26.12.2012,  21:52 Найти цитируемый пост)
Этот обработчик возвращает статус : "продолжить работу дальше или нет?" имея подобный ответ мы либо бросаем исключение

почему эта стратегия должна быть частью логики парсера ?  продолжение работы после исключения - это никак не внутренняя ответственность обьекта, именнуемого парсер..

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

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)