Быстрый способ создать функцию в точке ее использования
std::vector<int> v;
…
auto it = std::find_if(v.cbegin(), v.cend(),
[](int i) { return i > 0 && i < 10; });
Генерирует примерно следующее:
class MagicType1 {
public:
bool operator()(int i) const { return i > 0 && i < 10; }
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType1());
Быстрый способ создать функцию в точке ее использования
std::map<Solution*, int> mapSolID;
...
int someSolId = ...;
...
itInt = std::find_if(
mapSolID.begin(), mapSolID.end(), ???
);
class IsEqualSolID
{
public:
int m_nTarget;
IsEqualSolID(int nTarget):m_nTarget(nTarget) {}
bool operator ()(const std::pair<Solution*, int>& a)
{
return a.second == m_nTarget;
}
};
Быстрый способ создать функцию в точке ее использования
std::map<Solution*, int> mapSolID;
...
int someSolId = ...;
...
itInt = std::find_if(
mapSolID.begin(), mapSolID.end(), IsEqualSolID(someSolId))
);
class IsEqualSolID
{
public:
int m_nTarget;
IsEqualSolID(int nTarget):m_nTarget(nTarget) {}
bool operator ()(const std::pair<Solution*, int>& a)
{
return a.second == m_nTarget;
}
};
Быстрый способ создать функцию в точке ее использования
std::map<Solution*, int> mapSolID;
...
int someSolId = ...;
...
itInt = std::find_if(mapSolID.begin(), mapSolID.end(),
[&someSolId](const std::pair<Solution*, int>& a) {
return a.second = someSolId;
}
);
Функциональные объекты, задаваемые лямбдами,
называются замыканиями (closures).
Замыкания могут существовать вне исходногно контекста (scope):
std::function<bool(int)> returnClosure(int a) {
int b, c;
…
return [](int x) { int a*x*x + b*x + c == 0; };
}
auto f = returnClosure(10);
Вот тут
if(f(22) == true) { //... }
чему равны a, b, c?
Му же уже вышли из returnClosure
!
Со статическими и глобальными переменными все "нормально":
int a;
std::function<bool(int)> returnClosure() {
static int b, c;
…
return [](int x) { int a*x*x + b*x + c == 0; };
}
auto f = returnClosure(10);
А локльные переменные надо специально захватывать (capture).
Захваченная переменная копируется в closure. Следующий код
{
int minVal;
double maxVal;
…
auto it = std::find_if(v.cbegin(), v.cend(),
[minVal, maxVal](int i)
{ return i > minVal && i < maxVal; }
);
}
геренирует
class MagicType {
public:
MagicType(int v1, double v2): _minVal(v1), _maxVal(v2) {}
bool operator()(int i) const { return i > _minVal && i > _maxVal; }
private:
int _minVal;
double _maxVal;
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType(minVal, maxVal));
Захваченная переменная копируется в closure. Следующий код
{
int minVal;
double maxVal;
…
auto it = std::find_if(v.cbegin(), v.cend(),
[&minVal, &maxVal](int i)
{ return i > minVal && i < maxVal; }
);
}
геренирует
class MagicType {
public:
MagicType(int& v1, double& v2): _minVal(v1), _maxVal(v2) {}
bool operator()(int i) const { return i > _minVal && i > _maxVal; }
private:
int& _minVal;
double& _maxVal;
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType(minVal, maxVal));
Можно прописать захват всех переменных по умолчанию:
auto it = std::find_if( v.cbegin(), v.cend(), // default is
[=](int i) // by value
{ return i > minVal && i < maxVal; });
auto it = std::find_if( v.cbegin(), v.cend(), // default is
[&](int i) // by ref
{ return i > minVal && i < maxVal; });
При этом можно указать другой способ захвата для отдельных переменных:
auto it = std::find_if(v.cbegin(), v.cend(),
[=, &maxVal](int i)
{ return i > minVal && // minVal by value
i < maxVal; }); // maxVal by reference
Тип возвращаемого значания указывать не нужно, если:
Иначе надо указывать явно через "trailing return type syntax":
std::vector<double> v;
…
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double
{
makeLogEntry("std::transform", d);
return std::sqrt(std::abs(d));
}
);
Возвращаемы тип вычисляется автоматически (для всех функций). Аргументы лямбд тоже вычисляются.
auto magicValue1(int seed) // both returns are int
{
if (seed >= 0) return seed;
else return 42;
}
std::partition( d.begin(), d.end(),
[] (auto val) // ->bool not required, val type automatic
{ if (val % 10 == 0) return true;
return false; }
);
Init captures (захват через инициализацию)
return [ minVal = computeMinVal(this->minSeed) ] (int x)
{ return minVal <= x };
this
, по значению невозможно)[]{ //body }
Ранее мы рассматривали простые примеры, когда лямбда создается и сразу же используется в алгоритме stl.
std::vector<int> v;
…
auto it = std::find_if(v.cbegin(), v.cend(),
[](int i) { return i > 0 && i < 10; });
Когда мы обсуждали захват переменных, было понятно, что захват по значению нужен именно для отложенного запуска лямбд.
Как же хранить лямбды?
??? fn = [](MyClass* x) { return x->IsGood(); }
auto fn = [](MyClass* x) { return x->IsGood(); }
"Let's make it available everywhere!"
auto x1 = 10; // x1: int
std::map<int, std::string> m;
auto i1 = m.begin(); // i1: std::map<int, std::string>::iterator
for(auto i = m.begin(); i!=m.end(); i++) { i->second+="!"; }
"auto" переменные имеют тип инициализирующего их выражения.
Можно добавлять const/volatile и reference/pointer:
const auto *x2 = &x1; // x2: const int*
const auto& i2 = m; // i2: const std::map<int, std::string>&
auto ci = m.cbegin(); // ci: std::map<int, std::string>::const_iterator
Выведение типа аналогично выведению типа в шаблонах.
std::function<int(std::string&)> f; // f refers to callable entity
// compatible with given sig.
int someFunc(std::string&); // some function
f = someFunc; // f refers to someFunc
f = [](std::string &s)->unsigned
{ s += "!"; return s.size(); }; // f refers to a closure
class Gadget {
public:
int operator()(std::string&); // function call operator
…
};
Gadget g;
f = g; // f refers to g
std::function - универсальное представление вызываемого объекта
(callable entity)
bool myFn(int n, MyClass* obj) { //... }
std::function<bool(int, MyClass*)> fn = myFn;
bool myFn1(std::string str) { //... }
void myFn2(const char* str) { //... }
std::function<void(std::string)> fn = myFn1;
fn = myFn2;
class Button: public SomeGUIFrameworkBaseClass {
public:
…
using CallbackType = std::function<void(short)>;
void setCallback(const CallbackType& cb)
{
clickHandler = cb;
}
virtual void onClick(short upOrDown) // invoked by base class
{
clickHandler(upOrDown); // invoke function object
}
private:
CallbackType clickHandler;
};
Button b;
b.setCallback([](int v) { logClick(v); });
В Palign.dll был класс CSimplex, делающий минимизацию по многим переменным. Он принимал на вход функцию, которую нужно было минимизировать, и варьировал ее аргументы до достижения минимума.
typedef double (*FuncToMinimize) (double*);
class CSimplex
{
public:
CSimplex(FuncToMinimize, int, double*, double, bool _bound = false, ...);
...
};
В какой-то момент понадобилось передавать туда не функцию, а функтор, имеющий ссылки на другие объекты. В указатель на функцию такое в принципе не передашь - разве только вводить глобальные объекты.
public:
CSimplex(std::function<double(double*)>, int, double*, double, bool _bound = false, ...);
Альтернативное решение - передавать указатель на функтор IFuncToMinimize
с функцией virtual double operator() (double*)
Хранение генерирующих функций фабрики
class CReflectSeriazableFactoryFace {
public:
CReflectSeriazableFactoryFace();
virtual ISerializableFace* MakeSerializableObject(
GUID const& serialId );
virtual unsigned ObjectTypesCount() { return m_map.size(); }
virtual GUID GetObjectGUID(unsigned i);
protected:
std::map<GUID, std::function<IDocumentNode*()>> m_map;
}
CReflectSeriazableFactoryFace::CReflectSeriazableFactoryFace() {
m_map[SERID_IPOLYHEDRONNode] = []{ return new CModelNodeImpl; };
m_map[SERID_IReflectDataNode] = []{ return new CReflectDataNode; };
}
ISerializableFace* CReflectSeriazableFactoryFace::MakeSerializableObject(
GUID const& serialId ) {
auto creator = m_map.find(serialId);
if(creator == m_map.end()) return NULL;
IDocumentNode *node = creator->second();
if(!node) return NULL;
CDocumentNodeCommonSerializer * wrapper =
new CDocumentNodeCommonSerializer(node);
return wrapper;
}
Callback после чтения объекта сериализатором из файла
new CPolyhedronSer(m_phModel,
[this](POLYHEDRON* model) {
this->SetPOLYHEDRONByCloning(model);
delete model;
}
);
std::map<int, std::string> m;
typedef m::value_type MyMapVal;
for(auto i = m.begin(); i!=m.end(); i++) { i->second+="!"; }
std::foreach(m.begin(), m.end(), [](MyMapVal & i) { i.second+="!"; }
Чем второй вариант може быть лучше первого?
В случае с лямбдой мы работаем не с итератором, а со значением, так что не можем "испортить" итератор.
for(auto i = m.begin(); i!=m.end(); i++) { i->second+="!"; i++; }
std::foreach(m.begin(), m.end(), [](MyMapVal & i) { i.second+="!"; /*i++;*/ }
Функциональный стиль - максимально ограничиваем множество доступных коду данных, чтобы минимизировать побочные эффекты. В идеале вообще без переменных. Такой код надежнее и легче тестируется.
Коду внутри лямбд доступны только их параметры и захваченные переменные (поэтому лучше избегать [&]
и [=]
).
Примеров испльзования лямбд в STL-алгоритмах выше было множество.
С приходом лямбд можно расширить количество алгоритмов, которые мы повседневно используем, а не писать циклы вручную.
Неплохой язык для знакомства с функциональный программированием
Ресурсы:
Переменная не должна меняться по смыслу, но ее иницаилизация не укладывается в a = expr
.
const auto sortedInts = []()->std::vector<int> { // init const vector
std::vector<int> v(NUM_VALUES); // w/NUM_VALUES
std::iota(v.begin(), v.end(), 0-NUM_VALUES/2); // sequential ints
return v; // centered at 0
}();
const auto priority = [=]()->Priority {
makeLogEntry("Initializing priority");
auto cInfo = getCustomerInfo(customerID);
Priority p = (cInfo.salesInLast12Months() < bonusThreshhold)
? normalPriority
: highPriority;
return p;
}();
Это тоже трюк из функционального программирования - там по-другому нельзя.
class IDocumentNode : public IOctInterface {
public:
// List of sub-nodes
virtual unsigned ChildCount() const = 0;
virtual IDocumentNode* Child(unsigned i) = 0;
virtual void AddChild(IDocumentNode * pChild) = 0;
...
};
class TDocumentNodeLeaf : public IDocumentNode {
public:
virtual size_t ChildCount() const { return 0;} ;
virtual IDocumentNode* Child(size_t i) { return NULL; };
virtual void AddChild(IDocumentNode *) { return; }
...
};
class TDocumentNodeFolder : public TDocumentNodeLeaf {
public:
virtual size_t ChaildCount() const { return m_vChilds.size(); }
virtual IDocumentNode* Child(int i) {
return m_vChilds[i];
};
...
}
TDocumentNodeFolder obj;
obj.AddChild(something);
std::cout << obj.ChildCount(); // prints 0 !!!
class IDocumentNode : public IOctInterface {
public:
// List of sub-nodes
virtual unsigned ChildCount() const = 0;
virtual IDocumentNode* Child(unsigned i) = 0;
virtual void AddChild(IDocumentNode * pChild) = 0;
...
};
class TDocumentNodeLeaf : public IDocumentNode {
public:
virtual size_t ChildCount() const override { return 0;} ;
virtual IDocumentNode* Child(size_t i) override { return NULL; };
virtual void AddChild(IDocumentNode *) override { return; }
...
};
class TDocumentNodeFolder : public TDocumentNodeLeaf {
public:
virtual size_t ChaildCount() const override { return m_vChilds.size(); }
virtual IDocumentNode* Child(int i) override {
return m_vChilds[i];
};
...
}
error C3668: 'TDocumentNodeFolder::ChaildCount' : method with override specifier 'override' did not override any base class methods
С учетом vector | Без vector | |||
---|---|---|---|---|
2647 | OxygenCustomReports | 1089 | OxygenCustomReports | |
695 | Models | 339 | Reflect | |
663 | oxygeninclusionselectionplugin | 271 | Oxygen | |
654 | Reflect | 262 | oxygeninclusionselectionplugin | |
645 | GradingGIADll | 182 | GradingGIADll | |
554 | Oxygen | 150 | OxygenHWS | |
258 | OxygenHWS | 145 | Models | |
134 | Palign | 70 | Palign | |
87 | OHWS_Reflect2 | 59 | OHWS_Reflect2 | |
57 | Presentation | 52 | Presentation | |
42 | HPOxygenGUI | 27 | HPOxygenGUI | |
35 | TestPanel | 21 | TestPanel | |
20 | OxygenBatch | 13 | OxygenInterface | |
15 | OxygenInterface | 5 | SceneInfo | |
7 | OxExtScript | 5 | OxExtScript | |
6 | SceneInfo | 3 | OctInterface |
4379 | vector | 37 | function | 16 | multimap | 7 | forward | 3 | tstring | 2 | partition | |||||
1510 | string | 33 | swap | 16 | binary | 7 | allocator | 3 | search | 2 | log | |||||
314 | map | 31 | for | 14 | bidirectional | 6 | lower | 3 | reverse | 2 | hex | |||||
212 | pair | 30 | auto | 12 | valarray | 6 | istreambuf | 3 | less | 2 | greater | |||||
101 | find | 29 | ios | 12 | transform | 6 | boolalpha | 3 | cout | 2 | foreach | |||||
93 | ostream | 29 | ifstream | 11 | setprecision | 6 | 3 | abs | 2 | floor | ||||||
83 | make | 26 | max | 11 | memmove | 5 | unique | 2 | ws | 2 | ctype | |||||
82 | istream | 25 | char | 11 | advance | 5 | unary | 2 | use | 2 | count | |||||
74 | sort | 24 | ofstream | 10 | shared | 5 | streamsize | 2 | type | 2 | complex | |||||
56 | set | 24 | exception | 10 | istringstream | 5 | bind | 2 | strlen | 2 | collate | |||||
52 | tr | 21 | endl | 10 | fixed | 4 | sin | 2 | stable | 2 | accumulate | |||||
52 | numeric | 19 | remove | 10 | fill | 4 | PathString | 2 | sqrt | 1 | wstringstream | |||||
50 | list | 19 | min | 9 | ostringstream | 4 | memcpy | 2 | runtime | 1 | wstreambuf | |||||
44 | copy | 19 | back | 9 | bad | 4 | deque | 2 | rotate | 1 | wostringstream | |||||
44 | basic | 18 | wstring | 8 | getline | 4 | cos | 2 | replace | 1 | wostream | |||||
42 | stringstream | 18 | fabs | 7 | to | 4 | atan | 2 | queue | 1 | wofstream |
86 | Reflect | |
29 | HPOxygenGUI | |
25 | OxygenCustomReports | |
5 | oxygeninclusionselectionplugin | |
5 | OxExtScript | |
5 | Models | |
4 | TestPanel | |
4 | OxExtSript-share | |
4 | OHWS_Reflect2 | |
3 | Oxygen | |
3 | GradingGIADll | |
2 | OxygenHWS |