У меня есть крупномасштабная библиотека С++, написанная на С++ 98, которая интенсивно использует интерфейс С++ (точнее, классы С++ только с чистыми виртуальными функциями) для обработки событий. Теперь, видя, что мой код скомпилирован компилятором С++ 11/14, я думаю, могу ли я уменьшить шаблонный код, используя лямбду С++ 11 для замены реализации интерфейса.
В моей библиотеке есть несколько интерфейсов C++, у которых есть только один метод, например, следующий интерфейс, который мы использовали для определения простой задачи:
class SimpleTask
{
public:
virtual void run() = NULL;
};
Мое намерение состоит в том, чтобы использовать 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();
...
}
В Java 8 мы можем использовать Java lambda для реализации интерфейса с одним методом для написания более лаконичного кода, чем использование анонимного класса. Я провел небольшое исследование в С++ 11 и не нашел ничего подобного.
Поскольку код обработки событий моей библиотеки разработан в объектно-ориентированном шаблоне, а не в стиле функционального кодирования, есть ли способ использовать лямбда-выражение, чтобы помочь уменьшить этот код реализации интерфейса с одним методом?
new
не надо. Проходи за ходом или хотя бы ставь std::unique_ptr
SimpleTask
это не интерфейс. Не существует виртуального метода, не говоря уже о чисто виртуальном методе.
Если вы собираетесь модернизировать существующий C++, вам следует использовать современные методы. C++ изменился с 1998 года, когда большое значение имели интерфейсы и виртуальные функции. Более распространенный метод (используйте этот термин в широком смысле) заключается в использовании шаблонов и утиной печати. Если переданный объект имеет соответствующий интерфейс, вы можете его использовать. Для Lambda интерфейс похож на функтор, т.е. operator()(<Argument List>)
.
I did a bit research in C++11 and found there nothing similar to this
За исключением, может быть, C++ Lambda? [<CaptureList>](<ArgList>){<Code>}
Самая забавная декларация в C++ — это [](){}();
Проблема в том, что лямбды предназначены для работы с std::function<void()>
, а не SimpleTask
.
Спасибо за все входы/предложения. Код, который у меня был, был первоначально написан еще в 2005 году, и есть множество лучших шаблонов C++, которые я могу принять, например, использовать интеллектуальный указатель C++ вместо «нового», требуется время для модернизации всей библиотеки.
Извините, метод «запустить» должен иметь сигнатуру «виртуальный недействительный запуск () = 0;», я ошибся, когда писал вопрос.
Разве это не то, что вы ищете?
std::thread t(
[](){
std::cout << "thread\n"; // Here is the code run by the thread...
}
);
std::cout << "main\n";
t.join();
Возможно, покажите, где в вашей версии будет бит //Do somthing for this task
, чтобы было немного понятнее.
Не совсем. Java позволяет вам написать определение анонимного класса в строке при вызове метода. Таким образом, в этом случае код Java будет выглядеть, например. вот так: registerTask(new SimpleTask { void run() { \\run; }});
. std::thread
совершенно не связан с вопросом.
@Yksisarvinen OP прямо упомянул о необходимости лямбда, а не анонимного класса.
У OP, кажется, есть свой собственный класс Thread
, который принимает указатель SimpleTask
в своем конструкторе. Не уверен, как этот ответ связан
@Jean-BaptisteYunès Они упомянули лямбда-выражение Java, спросив, можно ли сделать что-то подобное на C++.
Спасибо за ваши предложения. Пример потока — лишь один из многих «обработчиков событий» или обратных вызовов в моей библиотеке. Так что использование std::thread не ответило на мой вопрос. Все равно цените. :-)
Вы можете создать оболочку, например:
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)};
}
А затем создать задачу:
auto task = makeSimpleTask([]() { });
Thread myThread(L"MyTestingThread", task);
Обратите внимание, что вам по-прежнему нужна оболочка и функция makeXXX
для каждого из ваших интерфейсов. В C++ 17 и более поздних версиях вы можете избавиться от функции makeXXX
, используя вывод аргумента шаблона класса. Избавиться от оболочки невозможно, но вы можете уменьшить шаблонный код, инкапсулировав некоторые вещи в макросы.
Вот пример макроса (не идеальный), который можно использовать для сокращения стандартного кода:
#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)}; }
А потом:
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);
Поскольку SimpleTask
упоминается как пример интерфейса, ваш ответ будет полным, если он будет передан как аргумент шаблона, а не жестко запрограммирован. Изменить настройку: и должны были быть реализованы только правильные makeXXX
функции.
@R2RT Согласен, но я сомневаюсь, что все интерфейсы совпадают void run()
. Я добавил некоторые детали в конце ответа.
Упс, верно. И последнее замечание: разве t
не следует перемещать в конструкторе, а makeSimpleTask
?
Должна ;) И тоже должна быть переслана в makeXXX
.
WRAPPER_FOR
может взять имя метода и вывести аргументы из типа &C::M
, а затем передать лямбду. И да, это покрывает опасный момент. Это раздражает с &
&&
const
volatile
noexcept
перегрузками.
@Yakk-AdamNevraumont Мне было бы интересно, как вы это сделаете для WRAPPER_FOR
... Я понятия не имею, как это сделать.
Спасибо за ваше решение, я думаю, что оно делает код немного более кратким. Я не могу понять весь ваш код (особенно часть макроса), но я попробую!
Старый стиль виртуального интерфейса:
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);
Более современный стиль:
Или Стиль печати утки:
// 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);
Глядя на ваш пример (который, кстати, явно не С++)
// 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();
...
}
Теперь давайте перепишем, используя утиную печать:
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();
...
}
Спасибо за все ваши предложения, мне нужно обновить свои современные знания С++, прежде чем я смогу полностью понять ваше решение, ценю его!
Это способ сделать отличный ответ @Holt с немного лучшим синтаксисом. Он не полный, потому что нужно сделать шаблон.
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)... ));
}
};
Вам понадобится 3 уточнения ref, 2 уточнения const, 2 уточнения volatile, умножение noexcept true/false, всего 24 различных версии.
теперь представьте себе макрос:
#define RUN_AS_LAMBDA_TYPE( CLASS ) \
run_as_lambda_impl< CLASS >
это жестко кодирует run
как метод, который мы переопределяем, но не жестко кодирует подпись. Также мы печатаем стирание лямбды, но пока меня это устраивает.
Мы можем обойти это.
#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) )
это 24 различных LAMBDA_TEMPLATE_IMPL
вызова для 3*2*2*2*2 типов переопределений методов. Вы можете уменьшить это:
#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) )
Потом:
#define LAMBDA_TEMPLATE( NAME, METHOD ) \
BASE_LAMBDA_TEMPLATE( NAME, METHOD ); \
LAMBDA_TEMPLATE_IMPL_CVRE( NAME, METHOD )
что 3+3+3+3+4 = 16 строк вместо 24.
Предположим, у вас есть эти два интерфейса:
class SimpleTask {
public:
virtual void run() = 0;
};
class ComplexTask {
public:
virtual int do_stuff(int, double) = 0;
};
тогда вы можете написать
LAMBDA_TEMPLATE( LambdaRun, run );
LAMBDA_TEMPLATE( LambdaDoTask, do_task );
и мы можем использовать LambdaRun<SimpleTask>{ []{std::cout << "I ran\n"; } }
как лямбда-реализацию SimpleTask
.
Аналогично LambdaDoTask<ComplexTask>{ [](auto a, auto b) { return a+b; } }
.
Это не очень похоже на Java. Java гораздо более объектно-ориентированный язык, чем C++; в C++ объектно-ориентированный дизайн — это вариант.
Лямбда-выражения C++ создают вызываемые объекты, которые переопределяют operator()
. Если у вас есть что-то, что «может быть запущено с подписью», идиоматический способ сделать это в C++ — использовать std::function<void()>
или аналогичный.
std::function
использует семантику значений; внутри вы можете сохранить указатель внутри значения, если хотите.
Итак, в С++ вам нужно:
using SimpleTask = std::function<void()>;
и остальная часть вашего кода теперь тривиальна:
Thread myThread(L"MyTestingThread", []{ /* code */} );
myThread.start();
потому что лямбда может быть напрямую преобразована в std::function<void()>
, если подписи совместимы.
Часть этого мигрирует к семантике значений.
Но за исключением этого, вы захотите
Спасибо за все ваши предложения!
на самом деле нет причин для тега Java