3

I'm writing a class to do timing analysis; it "remembers" the time of various points in the code and dumps its results on request.

It works.

But I'd like to have it dump the results by calling an existing logger function. I'm close but can't get it to compile. Here's a short version:

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 &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 baby\n");

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

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

I can't figure out how to pass the logger function to the TqTimingPoint constructor.

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

complains that ISO C++ forbids taking the address of a bound member function to form a pointer to member function. Say ‘&TheLogger::log’

while trying that:

TqTimingPoint tp("testCom", &TheLogger::log)

complains of

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

I'd like the TimingPoint class to accept any logger function as long as it has the same varargs signature. It shouldn't have to hard code TheLogger:: anywhere in it.

So, I suppose I need to cast TheLogger::log in main() but I none of the combinations I've tried work...

(If the logger function is defined at the global scope it works fine. Problems exist only with a log method within a class)

EDIT I

A few constraints I didn't mention before:

a) the logger class is not "mine" and cannot be modified. So stuck with the signature it provides. (and the real logger class is more complicated, and yes, does use a non-static log() member

b) Compiler is gcc -std=cxx+11

c) TqTimingPoint cannot be aware of (hardcode in) a specific logger class because there is a different, unrelated logger AnotherLogger which happens to have the same signature

Danny
  • 2,482
  • 3
  • 34
  • 48

5 Answers5

0

Casting function pointer to member function

So, I suppose I need to cast TheLogger::log in main()

No cast will help you here.

A pointer to a function cannot point to a non-static member function. Only a pointer to a member function of the type can do that. Pointers to member functions are not convertible to pointers to functions.

I'd like the TimingPoint class to accept any logger function as long as it has the same varargs signature.

TheLogger::log doesn't have the same signature, since it is a non-static member function. There is an implicit argument for the object on which the function is called i.e. this.


Given that TheLogger::log doesn't use any non-static member variables, it is unclear why it is a non-static member function. In case that's not necessary, a simpler solution is to make it static:

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

It could even be a non member function.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
0

After a bit of searching, it dawned on me: the problem is, of course the implicit this. Every member function has an implicit this member, which means you have to include type specification for the source class so the receiving pointer knows what you are actually passing in (and the compiler knows what it is allocating memory for).

The only way to make this work is either to make the source and receiving functions static, or mix this with inheritance so you can pass in a base class function pointer Base::foo to a virtual function and have it evaluate to any arbitrary Derived::foo.

Tzalumen
  • 652
  • 3
  • 16
0

Another way is to use std::function initialized with a lambda capture:

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", [&logger](char const* arg) { logger.log(arg); }};
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

The problem is that if the method of your class is neither static nor virtual then calling a method a.f(x) for a being a memeber of class A is roughly equivalent to calling A::f(&a,x) (no, that code won't work, but you've got the idea). The code which will work will be (a.*(&A::f))(x);

The fact that this is hidden should not confuse you. This is exactly the reason why compiler tells you 'no-no-no'.

Thus the most logical way is to pass the reference to your logger class instance. If you want the convenience you may do something like:

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

And change your constructor to

TqTimingPoint(const std::string &name, Logger & log )

You can make your logger function static (then you can pass its address), but then it would not be different from passing a pointer to a standalone function as static functions know nothing about your class.

Alexey Godin
  • 307
  • 1
  • 6
0

You can't call a member function just with a pointer to a member function; you also need to provide a pointer to the class that it comes from.

Casting a pointer to a member function to a regular function pointer is Undefined Behavior, and will probably cause your program to crash.

There's an easier way to do stuff.

Wrapping the logger with virtual functions

Let's look at what a logger base class would look like:

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

This base class has a log function; not much else to it. Now let's look at making it generic. This class will contain a specific logger (e.g, TheLogger), and it just calls log on it.

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

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

We can make it easy to use and avoid dynamic allocation by writing a small wrapper class. This WrapperClass takes anything, and stuffs it in a SpecificLogger.

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

And now TqTimingPoint is really easy to write.

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 &name, Logger& logger) :
        mName(name),
        mLogger(logger)
    { }

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

We can now have TheLogger in an entirely separate base class, and it doesn't have to know about the base class:

class TheLogger
{
public:
    TheLogger() {} 
    void log(const char* text, size_t size)
    {
        fwrite(text, 1, size, stdout); 
    }
};
Alecto Irene Perez
  • 10,321
  • 23
  • 46