Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++11 or above, Is there a way to implement a single-method pure virtual C++ interface by lambda?

Tags:

c++

c++11

lambda

I have a large scale C++ library written in C++ 98, which heavily uses C++ interface (precisely, C++ classes with only pure virtual functions) for event handling. Now seeing that my code are compiled by C++11/14 compiler, I'm thinking if I can reduce boilerplate code by using C++11 lambda to replace interface implementation.

In my library, there are some C++ interfaces who have only a single method, for example, the following interface us used to define a simple task:

class SimpleTask
{
public:
    virtual void run() = NULL;
};

My intention is to use C++ lambda to replace the old single-method interface implementation code like this:

void myFunction()
{
    ...

    class MySimpleTask : SimpleTask //An inline class to implement the iterface
    {
    public:
        void run() 
        {
            //Do somthing for this task
            ...    
            delete this;    //Finally, destroy the instance
        }
    };
    MySimpleTask * myThreadTask = new MySimpleTask();
    Thread myThread(L"MyTestingThread", myThreadTask);
    myThread.start();

    ...
}

In Java 8, we can use Java lambda to implement a single-method interface to write code more concise than using anonymous class. I did a bit research in C++11 and found there nothing similar to this.

Since my library's event handling code is designed in object oriented pattern, not in functional coding style, is there way to use lambda to help reduce those single-method interface implementation code?

like image 424
Hongkun Wang Avatar asked May 22 '19 14:05

Hongkun Wang


People also ask

Can you provide an implementation for a pure virtual method in C++?

You can provide an implementation for a pure virtual method in C++. “That’s crazy talk,” I hear you say. Okay, let’s start talking crazy: What happens when the test function constructs a Derived ? Trick question, because you get a linker error when you try to build the project.

What is a pure virtual function in C++?

In that article, I wrote that a pure virtual function is “a method which is declared by the base class, but for which no implementation is provided.” That statement is false. You can provide an implementation for a pure virtual method in C++. “That’s crazy talk,” I hear you say. Okay, let’s start talking crazy:

Should the prototype of virtual functions be same in base and derived?

The prototype of virtual functions should be same in base as well as derived class. They are always defined in base class and overridden in derived class. It is not mandatory for derived class to override (or re-define the virtual function), in that case base class version of function is used.

What are the rules for virtual functions in C++?

Rules for Virtual Functions. They Must be declared in public section of class. Virtual functions cannot be static and also cannot be a friend function of another class. Virtual functions should be accessed using pointer or reference of base class type to achieve run time polymorphism.


Video Answer


4 Answers

You can create a wrapper, e.g.:

class SimpleTask {
public:
    virtual void run() = 0;
};

// This class wraps a lambda (or any callable) and implement the run()
// method by simply calling the callable.
template <class T>
class LambdaSimpleTask: public SimpleTask {
    T t;

public:
    LambdaSimpleTask(T t) : t(std::move(t)) { }

    virtual void run() {
        t();
    }
};


template <class T>
auto makeSimpleTask(T &&t) {
    // I am returning a dynamically allocated object following your example,
    // but I would rather return a statically allocated one.
    return new LambdaSimpleTask<std::decay_t<T>>{std::forward<T>(t)};
}

And then to create the task:

auto task = makeSimpleTask([]() { });
Thread myThread(L"MyTestingThread", task);

Note that you still need to have a wrapper and a makeXXX function for each one of your interface. With C++17 and above, you can get rid of the makeXXX function by using class template argument deduction. Getting rid of the wrapper is not possible but you might be able to reduce the boilerplate code by encapsulating some stuff in macros.


Here is an example macro (not perfect) that could be used to reduce the boilerplate code:

#define WRAPPER_FOR(C, M, ...)                       \
    template <class T>                               \
    class Lambda##C: public C {                      \
        T t;                                         \
    public:                                          \
        Lambda##C(T t) : t(std::move(t)) { }         \
        virtual M { return t(__VA_ARGS__); }         \
    };                                               \
    template <class T> auto make##C(T &&t) {         \
        return Lambda##C<std::decay_t<T>>{std::forward<T>(t)}; }

And then:

class SimpleTask {
public:
    virtual void run() = 0;
};

class ComplexTask {
public:
    virtual int run(int, double) = 0;
};

WRAPPER_FOR(SimpleTask, void run());
WRAPPER_FOR(ComplexTask, int run(int a, double b), a, b);
like image 182
Holt Avatar answered Oct 23 '22 13:10

Holt


Isn't it what you are looking for?

std::thread t(
  [](){
    std::cout << "thread\n"; // Here is the code run by the thread...
  }
);
std::cout << "main\n";
t.join();
like image 4
Jean-Baptiste Yunès Avatar answered Oct 23 '22 14:10

Jean-Baptiste Yunès


Old Virtual interface style:

struct MyInterface {
    virtual Type action(argList)  = 0;
};

class MyClassThatUsesInterface
{
    MyInterface&   interface;
    public:
        MyClassThatUsesInterface(MyInterface& ref)
            : interface(ref)
        {}
        Type doStuff(argList)
        {
             return interface.action(argList);
        }
};
...
MyInterfaceImplementation injectedInterface;
MyClassThatUsesInterface  worker(injectedInterface);
...
worker.doStuff(someStuff);

More Modern style:
Or Duck Typing Style:

// No need for an explicit interface definition.
// Any function that will work can be used
// Let the compiler decide if the used function (functor/lambda) works.

template<typename F>
class MyClassThatUsesLambda
{
    F   interface;
    public:
        MyClassThatUsesLambda(F&& ref)
            : interface(std::move(ref))
        {}
        Type doStuff(argList)
        {
             return interface(argList);
             // Will compile if the type F supports function like operations.
             // This means a:
             //   * function pointer.
             //   * std::function
             //   * A type the overloads operator()
             //   * Lambda
        }
};
template<typename F>
MyClassThatUsesLambda<F> make_MyClassThatUsesLambda(F&& f) {return MyClassThatUsesLambda<F>(std::move(f));}
...
auto  worker = make_MyClassThatUsesLambda([](argList){/* Some Stuff*/});
...
worker.doStuff(someStuff);

Looking at your example (Which is obviously not C++ by the way)

// Added C++ required virtuals etc:
// Some basic memory management (not checked).
class SimpleTask
{
    public:
        virtual void run() = 0;
};
// Guessed at this object.
class Thread
{
    std::string                    name;
    std::unique_ptr<SimpleTask>    task
    public:
        Thread(std::string const& name, std::unique_ptr<SimpleTask>&& task)
            : name(name)
            , task(std:move(task))
        {}
        void start() {
            task.run();
        }
};
void myFunction()
{
    class MySimpleTask: public SimpleTask
    {
        public:
            virtual void run() override
            {
                //Do something for this task
                ...
                // Destroying this is an exceptionally bad idea.
                // Let the owner destroy it.
                // I made the task hold it as an std::unique_ptr
                // To solve this.    
                // delete this;    //Finally, destroy the instance
            }
    };
    ...
    Thread myThread("MyTestingThread", std::make_unique<MySimpleTask>());
    myThread.start();
    ...
}

Now lets re-write using duck typing:

template<typename F>
class Thread
{
    std::string                    name;
    F                              task
    public:
        Thread(std::string const& name, F&& task)
            : name(name)
            , task(std:move(task))
        {}
        void start() {
            task();
        }
};
template<typename F>
Thread<F> make_Thread(std::string const& name, F&& f) {return Thread<F>(name, std::move(f));}
void myFunction()
{ 
    ...
    auto  myThread = make_Thread("MyTestingThread", [](argList){/* Do something for this task */});
    myThread.start();
    ...
}
like image 2
Martin York Avatar answered Oct 23 '22 14:10

Martin York


This is a way to do @Holt's excellent answer with a bit better syntax. It is not complete, because there is boilerplate to do.

template<class C, class M = decltype(&C::run)>
struct run_as_lambda_impl;

// non-const non-volatile non-ref qualified noexcept(false) case:
template<class C, class R, class...Args>
struct run_as_lambda_impl<C, R(C::*)(Args...)>: C {
  std::function<R(Args...)> f;
  R run(Args...) final override {
    return static_cast<R>(f( std::forward<Args>(args)... ));
  }
};

You'll need 3 ref qualifies, times 2 const qualifies, times 2 volatile qualifies, times noexcept true/false for a total of 24 different versions of this.

now imagine a macro:

#define RUN_AS_LAMBDA_TYPE( CLASS ) \
  run_as_lambda_impl< CLASS >

this hard codes run as the method we override, but does not hard code the signature. Also we type erase the lambda, but I'm ok with that for now.

We can get around this.

#define BASE_LAMBDA_TEMPLATE( NAME, METHOD ) \
  template<class C, class M = decltype(&C::METHOD)> \
  struct NAME

#define LAMBDA_TEMPLATE_IMPL( NAME, METHOD, QUALS ) \
  template<class C, class R, class...Args> \
  struct NAME<C, R(C::*)(Args...) QUALS> : C { \
    std::function<R(Args...)> f; \
    NAME( std::function<R(Args...)> fin ): f(std::move(fin)) {} \
    R METHOD(Args...) QUALS final override { \
      return static_cast<R>( f( std::forward<Args>(args)... ) ); \
    } \
  }

  #define LAMBDA_TEMPLATE( NAME, METHOD ) \
    BASE_LAMBDA_TEMPLATE( NAME, METHOD ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, & ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, && ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, const ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, const & ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, const && ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile & ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile && ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile const ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile const & ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile const && ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, & noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, && noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, const noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, const & noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, const && noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile & noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile && noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile const noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile const & noexcept(true) ); \
    LAMBDA_TEMPLATE_IMPL( NAME, METHOD, volatile const && noexcept(true) )

which is 24 different LAMBDA_TEMPLATE_IMPL calls for the 3*2*2*2*2 types of method overrides. You could reduce this:

#define LAMBDA_TEMPLATE_IMPL_C( NAME, METHOD, QUALS ) \
  LAMBDA_TEMPLATE_IMPL( NAME, METHOD, QUALS ); \
  LAMBDA_TEMPLATE_IMPL( NAME, METHOD, const QUALS )

#define LAMBDA_TEMPLATE_IMPL_CV( NAME, METHOD, QUALS ) \
  LAMBDA_TEMPLATE_IMPL_C( NAME, METHOD, QUALS ); \
  LAMBDA_TEMPLATE_IMPL_C( NAME, METHOD, volatile QUALS )

#define LAMBDA_TEMPLATE_IMPL_CVR( NAME, METHOD, QUALS ) \
  LAMBDA_TEMPLATE_IMPL_CV( NAME, METHOD, QUALS ); \
  LAMBDA_TEMPLATE_IMPL_CV( NAME, METHOD, & QUALS ); \
  LAMBDA_TEMPLATE_IMPL_CV( NAME, METHOD, && QUALS )

#define LAMBDA_TEMPLATE_IMPL_CVRE( NAME, METHOD ) \
  LAMBDA_TEMPLATE_IMPL_CVR( NAME, METHOD,  ); \
  LAMBDA_TEMPLATE_IMPL_CVR( NAME, METHOD, noexcept(true) )

Then:

  #define LAMBDA_TEMPLATE( NAME, METHOD ) \
    BASE_LAMBDA_TEMPLATE( NAME, METHOD ); \
    LAMBDA_TEMPLATE_IMPL_CVRE( NAME, METHOD )

which is 3+3+3+3+4 = 16 lines instead of 24.


Suppose you have these two interfaces:

class SimpleTask {
public:
  virtual void run() = 0;
};

class ComplexTask {
public:
  virtual int do_stuff(int, double) = 0;
};

then you can write

LAMBDA_TEMPLATE( LambdaRun, run );
LAMBDA_TEMPLATE( LambdaDoTask, do_task );

and we can use LambdaRun<SimpleTask>{ []{std::cout << "I ran\n"; } } as a lambda-based implementation of SimpleTask.

Similarly, LambdaDoTask<ComplexTask>{ [](auto a, auto b) { return a+b; } }.


This isn't very Java-like. Java is a far more OO-centric language than C++; in C++, OO design is an option.

C++ lambdas create invokable objects that override operator(). If you have something that is "can be run with a signature", the idiomatic way to do it in C++ is to use a std::function<void()> or similar.

std::function uses value semantics; internally you can store a pointer within the value if you want.

So in C++ you'd want:

using SimpleTask = std::function<void()>;

and the rest of your code is now trivial:

Thread myThread(L"MyTestingThread", []{ /* code */} );
myThread.start();

because the lambda can be directly converted-to a std::function<void()> if the signatures are compatible.

A part of this is migrating to value-semantics.

But barring that, you'll want to

like image 1
Yakk - Adam Nevraumont Avatar answered Oct 23 '22 13:10

Yakk - Adam Nevraumont