Приведение указателя функции к функции-члену

#c

#c

Вопрос:

Я пишу класс для выполнения временного анализа; он «запоминает» время различных точек кода и выдает свои результаты по запросу.

Это работает.

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

 class TheLogger
{
public:
    TheLogger() {} 
    void log(const char* format, ...)
    {
        va_list argptr;
        va_start(argptr, format);
        vfprintf(stderr, format, argptr);
        va_end(argptr);
    }
};

// And there's a different unrelated logger with same signature
class AnotherLogger
{
public:
    TheLogger() {} 
    void logMessage(const char* format, ...)
    {
        va_list argptr;
        va_start(argptr, format);
        vsprintf(buf, format, argptr);
        va_end(argptr);
        doSomething(buf);
    }
};

class TqTimingPoint
{
public:
    TqTimingPoint(const std::string amp;name, void (*logger)(const char* format, ...) ) :
        mName(name),
        mpLoggerFcn(logger)
    { }

    void dump()
    {
        (this->mpLoggerFcn)( mName.c_str() );
    }

private:
    std::string               mName;   // Name of this collector

    void (*mpLoggerFcn)(const char* format, ...);
};

int
main(int, char **)
{
    TheLogger *pLogger = new TheLogger;
    pLogger->log("yo babyn");

    TqTimingPoint tp1("testCom", amp;TheLogger::log);

    AnotherLogger *pLogger2 = new AnotherLogger;
    TqTimingPoint tp2("testCom", amp;AnotherLogger::logMessage);
}
  

Я не могу понять, как передать функцию logger TqTimingPoint конструктору.

TqTimingPoint tp("testCom", amp;pLogger->log);

жалуется, что ISO C forbids taking the address of a bound member function to form a pointer to member function. Say ‘amp;TheLogger::log’

при попытке этого:

TqTimingPoint tp("testCom", amp;TheLogger::log)

жалуется на

no matching function for call to ‘TqTimingPoint::TqTimingPoint(const char [8], void (TheLogger::*)(const char*, ...))’

Я бы хотел, чтобы класс TimingPoint принимал любую функцию logger, если она имеет ту же сигнатуру varargs. Нигде в нем не должно быть жесткого кода TheLogger:: .

Итак, я полагаю, мне нужно привести TheLogger::log в main(), но ни одна из комбинаций, которые я пробовал, не работает…

(Если функция регистратора определена в глобальной области видимости, она работает нормально. Проблемы существуют только с log методом внутри класса)

РЕДАКТИРОВАТЬ я

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

a) класс logger не является «моим» и не может быть изменен. Итак, застрял с сигнатурой, которую он предоставляет. (и реальный класс logger сложнее, и да, использует нестатический log() член

б) Компилятором является gcc -std=cxx 11

c) TqTimingPoint не может быть осведомлен (жестко закодирован в) о конкретном классе регистратора, потому что существует другой, не связанный регистратор, AnotherLogger который, оказывается, имеет ту же сигнатуру

Комментарии:

1. Вы не можете этого сделать. Почему вы не передаете экземпляр logger?

2. Статичность вашего метода позволит передавать его как указатель на функцию. Теперь остается решить, имеет ли это какой-либо смысл…

3. Вот почему вам нужно передать лямбда-код обратного вызова, а не то, что вы делаете.

4. И вам следует использовать шаблоны переменных вместо va, если у вас есть компилятор C 11.

5. Использовать std::function .

Ответ №1:

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

Единственный способ заставить это работать — либо сделать исходную и принимающую функции статическими, либо смешать это с наследованием, чтобы вы могли передать указатель функции базового класса Base::foo на виртуальную функцию и заставить ее вычислять любое произвольное значение Derived::foo .

Ответ №2:

Другой способ — использовать std::function инициализированный с помощью лямбда-захвата:

 struct TheLogger {
    void log(const char* format, ...);
};

struct TqTimingPoint {
    std::string name;
    std::function<void(char const*)> logger_fn;

    void dump() { logger_fn( name.c_str() ); }
};

int main() {
    TheLogger logger;
    TqTimingPoint tp{"testCom", [amp;logger](char const* arg) { logger.log(arg); }};
}
  

Ответ №3:

Проблема в том, что если метод вашего класса не является ни статическим, ни виртуальным, то вызов метода a.f(x) для a, являющегося членом класса A, примерно эквивалентен вызову A::f(amp;a,x) (нет, этот код не будет работать, но вы уловили идею). Код, который будет работать, будет (a.*(amp;A::f))(x);

Тот факт, что this это скрыто, не должен вас смущать. Именно по этой причине компилятор говорит вам «нет-нет-нет».

Таким образом, наиболее логичным способом является передача ссылки на экземпляр вашего класса logger. Если вам нужно удобство, вы можете сделать что-то вроде:

 class Logger
{
public:
    void operator ()(const char * fmt, ...)
    {
      // do your business here
    }
};
  

И измените свой конструктор на

 TqTimingPoint(const std::string amp;name, Logger amp; log )
  

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

Ответ №4:

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

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

Есть более простой способ сделать это.

Обертывание регистратора виртуальными функциями

Давайте посмотрим, как будет выглядеть базовый класс logger:

 class LoggerBase {
   public:
    virtual void log(const char* text, size_t size) const = 0;
    virtual ~LoggerBase() {}
};
  

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

 template<class Logger>
class SpecificLogger : public LoggerBase {
   private:
    Logger* logger; //Pointer to actual logger
   public:
    SpecificLogger(Loggeramp; logger) : logger(amp;logger) {}

    void log(const char* text, size_t size) const override {
        logger->log(text, size); 
    }
};
  

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

 class LoggerWrapper {
   private:
    struct alignas(sizeof(SpecificLogger<LoggerBase>)) {
        char data[sizeof(SpecificLogger<LoggerBase>)]; 
    };
    LoggerBase constamp; getLogger() const {
        return *(LoggerBase const*)data; 
    }
   public:
    template<class Logger>
    LoggerWrapper(Loggeramp; logger) {
        new (data) SpecificLogger<Logger>(logger); 
    }
    void log(const char* text, size_t size) const {
        getLogger().log(text, size); 
    }
};
  

И теперь TqTimingPoint это действительно легко написать.

 class TqTimingPoint
{
   private:
    std::string   mName;   // Name of this collector
    LoggerWrapper mLogger;             

   public:
    // Logger can be *anything* with a log function 
    // that takes a const char* and a size
    template<class Logger>
    TqTimingPoint(const std::string amp;name, Loggeramp; logger) :
        mName(name),
        mLogger(logger)
    { }

    void dump()
    {
        mLogger.log(mName.data(), mName.size()); 
    }
};
  

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

 class TheLogger
{
public:
    TheLogger() {} 
    void log(const char* text, size_t size)
    {
        fwrite(text, 1, size, stdout); 
    }
};
  

Ответ №5:

Приведение указателя функции к функции-члену

Итак, я полагаю, мне нужно привести логгер::войти в main()

Здесь вам не поможет приведение.

Указатель на функцию не может указывать на нестатическую функцию-член. Это может сделать только указатель на функцию-член типа. Указатели на функции-члены не преобразуются в указатели на функции.

Я бы хотел, чтобы класс TimingPoint принимал любую функцию logger, если она имеет ту же сигнатуру varargs.

TheLogger::log не имеет такой же подписи, поскольку это нестатическая функция-член. Существует неявный аргумент для объекта, для которого вызывается функция, т.е. this .


Учитывая, что TheLogger::log не использует никаких нестатических переменных-членов, неясно, почему это нестатическая функция-член. В случае, если в этом нет необходимости, более простое решение — сделать его статическим:

 class TheLogger
{
public:
    static void log(const char* format, ...)
  

Это может быть даже функция, не являющаяся членом.

Комментарии:

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

2. @PasserBy хм, да. Я забыл, что вам нужно передать va_list функцию, которая принимает va_list