Могу ли я создавать анонимные классы на С++ и захватывать внешние переменные, например, в Java?
В Java, когда мне нужна функция обратного вызова, я должен реализовать анонимный класс. Внутри анонимного класса я могу получить доступ к внешним переменным, если они final
.
Теперь я делаю то же самое в С++. Я понимаю, что С++ лямбда работает лучше, но иногда мне нужно передать множество функций, где с анонимными классами мне нужно передать только один экземпляр.
Я попробовал следующий пример. Он работает с GCC 4.3.4.
class IA {
public:
virtual int f(int x) = 0;
};
int main() {
class : public IA {
int f(int x) { return x + 1; }
} a;
doFancyWork(&a);
return 0;
}
Можно ли захватить внешние переменные, подобные этому?
int main() {
int y = 100; // mark y as final if possible
class : public IA {
int f(int x) { return x + y; }
} a;
return 0;
}
UPDATE:
Второй пример не будет компилироваться. Ошибки здесь,
prog.cpp: In member function ‘virtual int main()::<anonymous class>::f(int)’:
prog.cpp:9: error: use of ‘auto’ variable from containing function
prog.cpp:7: error: ‘int y’ declared here
prog.cpp: In function ‘int main()’:
prog.cpp:7: warning: unused variable ‘y’
UPDATE:
Я только что осознал еще несколько проблем:
- Я не могу написать конструктор, потому что класс не имеет имени
- список инициализаторов не позволяет наследовать.
- любое изменение для компиляции делает код нечитаемым.
Думаю, мне нужно отойти от анонимных классов.
Ответы
Ответ 1
Нет возможности автоматически фиксировать эти переменные, но вы можете использовать альтернативный подход. Это если вы хотите захватить по ссылке:
int main() {
int y = 100; // mark y as final if possible
class IB : public IA {
public:
IB(int& y) : _y(y) {}
int f(int x) { return x + _y; }
private:
int& _y;
} a (y);
return 0;
}
Если вы хотите записать по значению, просто измените int&
на int
.
Во всяком случае, вы можете использовать кортеж lambdas как объект с несколькими обратными вызовами, если это вас беспокоит о отдельных лямбдах. У вас все равно будет все, что упаковано в один объект, и захват будет сделан бесплатно.
Как пример:
auto callbacks = make_tuple(
[] (int x) { cout << x << endl; },
[&] () { cout << y << endl; }, // y is captured by reference
[=] (int x) { cout << x + y << endl; }, // y is captured by value
// other lambdas here, if you want...
);
Ответ 2
Вы можете захватить переменную вручную (что похоже на то, что делает захват лямбда за кулисами):
int main() {
int y = 100;
struct {
int& y;
int operator()(int x) { return x + y; }
} anon = { y };
}
Затем вы можете использовать его следующим образом:
#include <iostream>
...
std::cout << anon(10) << std::endl;
Печатает 110, как ожидалось. К сожалению, вы не можете наследовать анонимный тип от другого с помощью этого метода, поскольку конструктивные типы инициализатора не могут наследовать другого типа. Если наследование имеет решающее значение, вы должны использовать метод конструктора описанный Энди Проулом.
Ответ 3
С++ лямбда может захватывать "внешние" переменные. [Редактировать: когда я впервые прочитал вопрос, я как-то пропустил, где он упомянул, что знает о лямбдах. К лучшему или худшему, С++ не имеет ничего, что действительно напоминает анонимный класс].
Например:
#include <iostream>
int main(){
int y = 100;
auto lambda = [=](int x) { return x + y; };
std::cout << lambda(2);
}
... выводит 102
в качестве вывода.
Обратите внимание, что хотя он выглядит несколько как функция, С++ лямбда действительно приводит к созданию класса. Полагаю, я должен добавить: этот класс не является технически анонимным, но имеет некоторое неуказанное имя, которое никогда не отображается напрямую.
Изменить: я все еще немного озадачен тем, что не использовал lambdas. Является ли намерение использовать один класс, который содержит много функций-членов? Если это так, неясно, как вы планируете указывать, какую функцию-член нужно вызывать, в какое время/для какой цели. Моя немедленная реакция заключается в том, что это звучит подозрительно, как будто вы пытаетесь перевернуть язык, чтобы поддержать проблемный дизайн.
Ответ 4
Это не анонимность класса, который ограничивает доступ к внешним переменным. В вопросе y недоступен, потому что класс был определен локально внутри функции.
Существует несколько ограничений для классов, определенных локально. Во-первых, они могут обращаться к локальным переменным, которые являются статическими, но могут обращаться к любой другой переменной, доступной для области действия функции. Кроме того, локальные классы не могут иметь статических членов данных.
Что касается анонимных классов, вы не можете иметь конструкторы и деструкторы. Все функции-члены должны быть объявлены внутри определения класса. Он не может содержать статические статические члены, в том числе константные статические интегральные элементы, которые обычно могут быть созданы внутри определения класса. Также наследование не допускается.
Анонимные классы являются неясным углом С++ и имеют мало практического значения. Лямбда-функции и другие методы намного более гибкие. Но кто знает, возможно, в некоторых ситуациях это могло бы помочь с чтением кода.
Ответ 5
Если ваш класс IA
действительно имеет только один виртуальный метод, который вам нужно переопределить (и реальная сложность - это другие не виртуальные методы), но вы не хотите фиксировать локальные переменные, которые этот метод требует, как насчет это:
int main() {
int y = 100;
auto f = [=](int x){return x+y;};
typedef decltype(f) F;
struct IB : IA {
F _f;
IB(F _f): _f(_f) {}
int f(int x) { return _f(x); }
} a(f);
doFancyWork(&a);
return 0;
}