简体   繁体   English

在 C++11 或更高版本中,有没有办法通过 lambda 实现单方法纯虚拟 C++ 接口?

[英]In C++11 or above, Is there a way to implement a single-method pure virtual C++ interface by 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.我有一个用 C++ 98 编写的大型 C++ 库,它大量使用 C++ 接口(准确地说,只有纯虚函数的 C++ 类)进行事件处理。 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.现在看到我的代码是由C++11/14编译器编译的,我在想是否可以通过使用C++11 lambda来替换接口实现来减少样板代码。

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:在我的库中,有一些 C++ 接口只有一个方法,例如,我们用来定义一个简单任务的以下接口:

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:我的意图是使用 C++ lambda 来替换旧的单方法接口实现代码,如下所示:

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.在 Java 8 中, 我们可以使用 Java lambda 来实现单方法接口来编写比使用匿名类更简洁的代码。 I did a bit research in C++11 and found there nothing similar to this.我在 C++11 中做了一些研究,发现没有类似的东西。

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?由于我的库的事件处理代码是以面向对象的模式设计的,而不是以函数式编码风格设计的,有没有办法使用 lambda 来帮助减少那些单方法接口实现代码?

You can create a wrapper, eg:您可以创建一个包装器,例如:

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.请注意,您仍然需要为每个界面提供一个包装器和一个makeXXX函数。 With C++17 and above, you can get rid of the makeXXX function by using class template argument deduction.在 C++17 及更高版本中,您可以通过使用类模板参数推导来摆脱makeXXX函数。 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);

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();

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)看看你的例子(顺便说一下,这显然不是 C++)

// 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();
    ...
}

This is a way to do @Holt's excellent answer with a bit better syntax.这是一种用更好的语法来做@Holt 的优秀答案的方法。 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.您需要 3 次 ref 限定,2 次 const 限定,2 次 volatile 限定,次 noexcept 真/假,总共 24 个不同版本。

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.这个硬代码作为我们覆盖的方法run ,但不会对签名进行硬编码。 Also we type erase the lambda, but I'm ok with that for now.我们还输入了擦除 lambda,但我现在可以接受。

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.这是对 3*2*2*2*2 类型的方法覆盖的 24 个不同的LAMBDA_TEMPLATE_IMPL调用。 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.这是 3+3+3+3+4 = 16 行而不是 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"; } }我们可以使用LambdaRun<SimpleTask>{ []{std::cout << "I ran\\n"; } } LambdaRun<SimpleTask>{ []{std::cout << "I ran\\n"; } } as a lambda-based implementation of SimpleTask . LambdaRun<SimpleTask>{ []{std::cout << "I ran\\n"; } }作为一个基于拉姆达实现SimpleTask

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


This isn't very Java-like.这不是很像 Java。 Java is a far more OO-centric language than C++; Java 是一种远比 C++ 以 OO 为中心的语言; in C++, OO design is an option .在 C++ 中,OO 设计是一种选择

C++ lambdas create invokable objects that override operator() . C++ lambdas 创建可调用对象来覆盖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.如果您有“可以使用签名运行”的东西,那么在 C++ 中执行此操作的惯用方法是使用std::function<void()>或类似方法。

std::function uses value semantics; std::function使用值语义; internally you can store a pointer within the value if you want.如果需要,您可以在内部在值中存储一个指针。

So in C++ you'd want:所以在 C++ 中你会想要:

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.因为如果签名兼容,可以将 lambda 直接转换为std::function<void()>

A part of this is migrating to value-semantics.其中一部分是迁移到值语义。

But barring that, you'll want to但除此之外,你会想要

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM