0

Let's say I have the following callback class:

class LogCallback {
public:
    virtual void sendLog(std::string log) = 0;
    virtual void setErrorCode(int code) = 0;
};

And I have the engine which accepts a callback implementation:

class Engine {
public:
    Engine();
    virtual ~Engine();
    void setCallback(LogCallback* callback);
    void start();
private:
    LogCallback* logCallback;
};

Now I can create an implementation class:

class OutLogger : public LogCallback {

    void sendLog(std::string log) {
        cout << "out: " << log << endl;
    }

    void setErrorCode(int code) {
        sendLog("error code " + std::to_string(code));
    }

};

And use it:

int process() {
    Engine engine;
    OutLogger out;
    engine.setCallback(&out);
    engine.start();
}

In C++11 I can also use an anonymous local class:

int anonymous() {
    Engine engine;
    class : public LogCallback {

        void sendLog(std::string log) {
            cerr << "err: " << log << endl;
        }

        void setErrorCode(int code) {
            sendLog("error code " + std::to_string(code));
        }

    } err;
    engine.setCallback(&err);
    engine.start();
}

Now there is the question: can I do the same without the explicit anonymous class, inside the function call?

If it was Java I would do it like this:

public class AnonymousClass {

    private abstract class LogCallback {

        abstract void sendLog(String log);

        abstract void setErrorCode(int code);
    }

    private class Engine {

        public void setCallback(LogCallback callback) {
            this.callback = callback;
        }

        public void start() {
            if (callback != null) {
                callback.sendLog("Starting...");
            }
        }

        private LogCallback callback;
    }

    public void process() {
        Engine engine = new Engine();
        engine.setCallback(new LogCallback() {
            @Override
            void sendLog(String log) {
                System.out.println("out: " + log);
            }

            @Override
            void setErrorCode(int code) {
                sendLog("error code " + code);
            }
        });
        engine.start();
    }

    public static void main(String[] args) {
        AnonymousClass example = new AnonymousClass();
        example.process();
    }

}
Holt
  • 36,600
  • 7
  • 92
  • 139
Cezariusz
  • 463
  • 8
  • 15

4 Answers4

3

Holt's comment above is correct. The only way to declare a new type in an expression like Java's new X() { ... } is a lambda expression, and you can't make that derive from your abstract base class.

The C++ way to do it would be to stop all that OOP inheritance ickiness and use a function template, or a function taking a type erased std::function, so it accepts any callable with a suitable call signature, then you could use a lambda.

For example:

class Engine {
public:
    enum class LogType { String, ErrorCode };
    using LogCallback = std::function<void(LogType, std::string, int)>;
    Engine();
    virtual ~Engine();

    void setCallback(LogCallback callback) { logCallback = callback; }

    void start() {
        if (logCallback)
            logCallback(LogType::String, "Starting...", 0);
    }

private:
    LogCallback logCallback;
};

int anonymous() {
    Engine engine;
    engine.setCallback([](Engine::LogType t, std::string log, int code) {
        if (t == Engine::LogType::ErrorCode)
             log = "error code " + std::to_string(code);
        cerr << "err: " << log << endl;
    });
    engine.start();
}

(A nicer definition for the LogCallback type would be:

using LogCallback = std::function<void(std::variant<std::string, int>)>;

but we don't have variant in C++ just yet.)

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Can you give me an example? – Cezariusz May 10 '16 at 10:26
  • 1
    Instead of having `LogCallback* logCallback` in `Engine`, you could have 2 `std::function` objects for `sendLog` and `sendErrorCode`, that you use as callbacks. – rozina May 10 '16 at 10:29
  • @rozina, since the OP's example shows one callback function using the other (`setErrorCode` uses `sendLog`), it probably makes sense to have a single callback that does both jobs. – Jonathan Wakely May 10 '16 at 10:34
1

You cannot implement interface in place, but you can create generic class with call back functions as parameters:

class GenericLogCallback : public LogCallback {
public:
    GenericLogCallback(std::function<void (std::string)> sendlog,
     std::function<void (int)> seterrorcode) : sendLog_(std::move(sendlog)),
   setErrorCode_(std::move(seterrorcode)) {}

    virtual void sendLog(std::string log) override {
       if(sendLog_) sendLog_(log);
    }
    virtual void setErrorCode(int code) override {
       if(setErrorCode_) setErrorCode_(code);
    }
private:
   std::function<void (std::string)> sendLog_;
   std::function<void (int)> setErrorCode_;
};

...
GenericLogCallback err([](std::string){},[](int){});
engine.setCallback(&err);
AnatolyS
  • 4,249
  • 18
  • 28
  • 1
    Preferably use this instead of `LogCallback`, no point having virtual dispatch if you're using `std::function` hiding. The engine would take this by value – M.M May 10 '16 at 10:42
1

The answers already posted here using std::function are good, but if you care a lot about performance you can do one better by directly storing the functors (which could be results of std::bind(), or C-style function pointers, or lambdas):

template <typename SendLog, typename SetErrorCode>
class GenericLogger : public LogCallback {
public:
    GenericLogger(SendLog sender, SetErrorCode setter)
        : m_sender(sender), m_setter(setter) {}

    void sendLog(std::string log) override {
        m_sender(log);
    }

    void setErrorCode(int code) override {
        m_setter(code);
    }

    SendLog m_sender;
    SetErrorCode m_setter;
};

template <typename SendLog, typename SetErrorCode>
GenericLogger<SendLog, SetErrorCode> makeLogger(SendLog sender, SetErrorCode setter) {
    return GenericLogger<SendLog, SetErrorCode>(sender, setter);
}

void sendLog(std::string log) {
    std::cout << "out: " << log << std::endl;
}

void setErrorCode(int code) {
    sendLog("error code " + std::to_string(code));
}

int main()
{
    Engine engine;
    auto out = makeLogger(
        [](std::string s){std::cout << "lambda: " << s << '\n';},
        setErrorCode);
    engine.setCallback(&out);
    engine.start();
}

The above avoids using std::function except when the actual arguments to makeLogger() are of that type. This reduces overhead by invoking exactly the given functor, rather than always storing a std::function.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

One thing you could do is create a wrapper class that allows to use lambdas:

template<class F1, class F2>
struct LogCallbackF : LogCallback {
    F1 f1;
    F2 f2;
    LogCallbackF(F1 f1, F2 f2) : f1(std::move(f1)), f2(std::move(f2)) {}

    void sendLog(std::string msg) override { f1(msg); }
    void setErrorCode(int code) override { f2(code); }
};

template<class F1, class F2>
inline LogCallbackF<F1, F2> make_log_callback(F1 f1, F2 f2) {
    return {std::move(f1), std::move(f2)};
}

void process() {
    Engine engine;
    auto callback = make_log_callback(
        [](std::string a) { std::cout << a << '\n'; },
        [](int a) { std::cout << a << '\n'; }
        );
    engine.setCallback(&callback);
    engine.start();
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271