本节分为四部分:
C++ STL 中的绑定器
function 示例和实现
bind 和 function 实现线程池
lambda 表达式
1. C++ STL 中的绑定器 1. 介绍绑定器 1 2 3 std::bind1st std::bind2nd
以上两个函数用于将一个 二元函数对象 转换为 一元函数对象 。
bind 意思为:绑定 ,1st 代表 first;2nd 代表 second,说明如下:
1 2 3 4 5 template <typename F, typename T>std::binder1st<F> bind1st (const F& f, const T& x) ;template <typename F, typename T >std::binder2nd<F> bind2nd ( const F& f, const T& x )
将给定的参数 x 绑定到 二元函数对象 F 的第一个或第二个形参。也就是说,将 x 存储在该包装器中,如果调用该包装器,则将 x 作为 F 的第一个或第二个形参传递。
bind1st
:相当于操作:x Operation value
== operator()
的第一个形参变量绑定成一个确定的值。bind2nd
:相当于操作:Operation value x
== operator()
的第二个形参变量绑定成一个确定的值。
2. 实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <iostream> #include <vector> #include <functional> #include <algorithm> #include <ctime> using namespace std;template <typename Container>void showContainer (Container& con) { typename Container::iterator it = con.begin (); for (; it != con.end (); it++) { cout << *it << " " ; } cout << endl; }int main () { vector<int > vec; srand (time (nullptr )); int i = 0 ; for (i = 0 ; i < 10 ; ++i) { vec.push_back (rand () % 100 + 1 ); } cout << "Init:" ; showContainer (vec); cout << "Sort:" ; sort (vec.begin (), vec.end (), greater <int >()); showContainer (vec); vector<int >::iterator it = find_if ( vec.begin (), vec.end (), bind1st (greater <int >(), 70 ) ); if (it != vec.end ()) { vec.insert (it, 70 ); } cout << "Insert:" ; showContainer (vec); return 0 ; }
3. 实现原理
find_if
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 _EXPORT_STD template <class _InIt , class _Pr >_NODISCARD _CONSTEXPR20 _InIt find_if ( _InIt _First, const _InIt _Last, _Pr _Pred ) { _Adl_verify_range(_First, _Last); auto _UFirst = _Get_unwrapped(_First); const auto _ULast = _Get_unwrapped(_Last); for (; _UFirst != _ULast; ++_UFirst) { if (_Pred(*_UFirst)) { break ; } } _Seek_wrapped(_First, _UFirst); return _First; }
其中第三个参数 _Pred
是一个一元函数对象
bind1st
源码:
1 2 3 4 5 6 7 8 9 10 11 _EXPORT_STD template <class _Fn , class _Ty >_NODISCARD binder1st<_Fn> bind1st ( const _Fn& _Func, const _Ty& _Left) { typename _Fn::first_argument_type _Val(_Left); return binder1st <_Fn>(_Func, _Val); }
它是一个函数模板,返回一元函数对象 binder1st
bind2nd
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 template <class _Fn > class binder1st : public unary_function< typename _Fn::second_argument_type, typename _Fn::result_type> { public : typedef unary_function< typename _Fn::second_argument_type, typename _Fn::result_type> _Base; typedef typename _Base::argument_type argument_type; typedef typename _Base::result_type result_type; binder1st ( const _Fn& _Func, const typename _Fn::first_argument_type& _Left) : op (_Func) , value (_Left) { } result_type operator () (const argument_type& _Right) const { return (op (value, _Right)); } result_type operator () (argument_type& _Right) const { return (op (value, _Right)); } protected : _Fn op; typename _Fn::first_argument_type value; };
一元函数对象默认构造函数接受两个参数, op
和 value
。赋值运算符接收一个参数,并使用传入的 op
和 vaule
进行运算:op(value, _Right)
4. 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #include <iostream> #include <vector> #include <functional> #include <algorithm> #include <ctime> using namespace std;template <typename Container>void showContainer (Container& con) { typename Container::iterator it = con.begin (); for (; it != con.end (); it++) { cout << *it << " " ; } cout << endl; }template <typename Iterator, typename Compare>Iterator my_find_if (Iterator first, Iterator last, Compare comp) { for (; first != last; first++) { if (comp (*first)) { return first; } } return last; }template <class Compare , class T >class _mybind1st {public : _mybind1st(Compare comp, T val) : _comp(comp) , _val(val) { } bool operator () (const T& second) { return _comp(_val, second); }private : Compare _comp; T _val; };template <typename Compare, typename T>_mybind1st<Compare, T> mybind1st (Compare comp, const T& val) { return _mybind1st<Compare, T>(comp, val); }int main () { vector<int > vec; srand (time (nullptr )); for (int i = 0 ; i < 20 ; ++i) { vec.push_back (rand () % 100 + 1 ); } cout << "Init:" ; showContainer (vec); sort (vec.begin (), vec.end (), greater <int >()); cout << "Sort:" ; showContainer (vec); vector<int >::iterator it1 = my_find_if ( vec.begin (), vec.end (), mybind1st (greater <int >(), 70 ) ); if (it1 != vec.end ()) { vec.insert (it1, 70 ); } cout << "Insert:" ; showContainer (vec); return 0 ; }
2. function 示例和实现 function 是一个函数包装器模板,最早来自 boost 库。在 c11 标准中将其纳入标准库。该函数包装器模板可以包装任何类型的可调用元素,例如普通函数和函数对象 。
function 最大的作用就是保留可调用元素的类型 。
解决绑定器,函数对象,lambda 表达式只能使用在一条语句中的问题;
定义:传递一个 _Fty function type
希望传递一个函数类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 template <class _Fty >class function : public _Get_function_impl<_Fty>::type { private : typedef typename _Get_function_impl<_Fty>::type _Mybase; public : function () noexcept { } }
1. function的应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <iostream> #include <vector> #include <functional> #include <algorithm> #include <ctime> #include <string> using namespace std;void hello1 () { cout << "hello world1!" << endl; }void hello2 (const string& str) { cout << str << endl; }int sum (int a, int b) { cout << "Sum:" ; return a + b; }class Test {public : void hello (string str) { cout << str << endl; } };int main () { function<void ()> func1 = hello1; func1 (); function<void (string)> func2 = hello2; func2 ("hello world2!" ); function<int (int , int )> func3 = sum; sum (2 , 3 ); function<int (int , int )> func4 = [](int a, int b) ->int { return a + b; }; cout << "function::operator():" << func4 (2 , 5 ) << endl; function<void (Test*, string)> func5 = &Test::hello; Test* t = new Test (); func5 (t, "call Test::hello!" ); return 0 ; }
1 2 3 4 function<void (Test*, string)> func5 = &Test::hello; func5 (&Test (), "call Test::hello!" );
原因:
其实是可以实现取地址的,对象是在站内构造的,可以通过栈偏移指向;但是编译器不支持,无法实现兼容。
报左值引用错误是因为仿函数 要用,上面初始化的仿函数是个 this call,要提供 this,而 this 是结构体的指针,无法通过取地址实现,而且临时构造不能允许取地址。
test()
的声明周期就那一行,结束就被销毁了是个右值,只有左值能取址。
2. 模板的完全特例化和部分特例化 对于下面的 compare,对于 char 类型的比较不能满足实际的需要,因此对 compare 特例化一个版本 compare<const char *>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <vector> #include <functional> #include <algorithm> #include <ctime> #include <string> using namespace std;template <typename T>bool compare (T a, T b) { cout << "template compare" << endl; return a > b; }template <>bool compare <const char *>(const char * a, const char * b) { cout << "compare<const char*>" << endl; return strcmp (a, b) > 0 ; }int main () { compare (10 , 20 ); compare ("aaa" , "bbb" ); return 0 ; }
匹配原则:有完全特例化则先匹配完全特例化,其次部分特例化,最后函数模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <iostream> #include <vector> #include <functional> #include <algorithm> #include <ctime> #include <string> using namespace std;template <class T >class Vector {public : Vector () { cout << "call Vector template init" << endl; } };template <> class Vector <char *> {public : Vector () { cout << "call Vector<char*> init" << endl; } };template <class Ty >class Vector <Ty*> {public : Vector () { cout << "call Vector<Ty*> init" << endl; } };template <class R , class A1 , class A2 >class Vector <R (*)(A1, A2)> {public : Vector () { cout << "call Vector<R(*)(A1, A2)> init" << endl; } };template <class R , class A1 , class A2 >class Vector <R (A1, A2)> {public : Vector () { cout << "call Vector<R(A1, A2)> init" << endl; } };int sum (int a, int b) { return a + b; }int main () { Vector<int > vec1; Vector<char *> vec2; Vector<int *> vec3; Vector<int (*)(int , int )> vec4; Vector<int (int , int )> vec5; typedef int (*PFUNC1) (int , int ) ; PFUNC1 pfunc1 = sum; cout << pfunc1 (10 , 20 ) << endl; typedef int PFUNC2 (int , int ) ; PFUNC2* pfunc2 = sum; cout << (*pfunc2)(10 , 20 ) << endl; return 0 ; }
实参推演:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <iostream> #include <typeinfo> using namespace std;int sum (int a, int b) { return a + b; }template <typename T>void func (T a) { cout << typeid (T).name () << endl; }template <typename R, typename A1, typename A2>void func2 (R(*a)(A1, A2)) { cout << typeid (R).name () << endl; cout << typeid (A1).name () << endl; cout << typeid (A2).name () << endl; }class Test {public : int sum (int a, int b) { return a + b; } };template <typename R, typename T, typename A1, typename A2>void func3 (R(T::* a)(A1, A2)) { cout << typeid (R).name () << endl; cout << typeid (T).name () << endl; cout << typeid (A1).name () << endl; cout << typeid (A2).name () << endl; }int main () { cout << "---------------------func1\n" ; func (10 ); func ("aaa" ); func (sum); func (&Test::sum); cout << "---------------------func2\n" ; func2 (sum); cout << "---------------------func3\n" ; func3 (&Test::sum); return 0 ; }
3. function 原理解析 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 _EXPORT_STD template <class _Fty >class function : public _Get_function_impl<_Fty>::type { private : using _Mybase = typename _Get_function_impl<_Fty>::type;public : function () noexcept {} function (nullptr_t ) noexcept {} function (const function& _Right) { this ->_Reset_copy(_Right); } template <class _Fx , typename _Mybase::template _Enable_if_callable_t<_Fx, function> = 0 > function (_Fx&& _Func) { this ->_Reset(_STD forward<_Fx>(_Func)); } function& operator =(const function& _Right) { function (_Right).swap (*this ); return *this ; } function (function&& _Right) noexcept { this ->_Reset_move(_STD move (_Right)); } }
function 底层是一个可变参的偏特化函数对象
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <iostream> #include <typeinfo> #include <string> #include <functional> using namespace std;void hello (string str) { cout << str << endl; }int sum (int a, int b) { return a + b; }template <class Fty >class myfunction {};template <class R , class A1 >class myfunction <R (A1)> {public : using PFUNC = R (*)(A1); myfunction (PFUNC pfunc) : _pfunc(pfunc) {} R operator () (A1 arg) { return _pfunc(arg); }private : PFUNC _pfunc; };template <typename R, typename ... A>class myfunction <R (A...)> {public : using PFUNC = R (*)(A...); myfunction (PFUNC pfunc) :_pfunc(pfunc) {} R operator () (A... arg) { return _pfunc(arg...); }private : PFUNC _pfunc; };int main () { myfunction<void (string) > func1 (hello) ; func1 ("hello world!" ); myfunction<int (int , int ) > func2 (sum) ; cout << func2 (10 , 20 ) << endl; return 0 ; }
3. bind 和 function 实现线程池 C++11 bind 绑定器,是一个函数模板 ,可以自动推演模板类型参数=> 返回的结果还是一个函数对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <typeinfo> #include <string> #include <memory> #include <vector> #include <functional> #include <thread> using namespace std;using namespace placeholders;void hello (string str) { cout << str << endl; }int sum (int a, int b) { return a + b; }class Test {public : int sum (int a, int b) { return a + b; } };int main () { cout << "----- bind -----" << endl; bind (hello, "hello bind 1!" )(); cout << bind (sum, 10 , 20 )() << endl; cout << bind (&Test::sum, Test (), 20 , 30 )() << endl; cout << "----- placeholder -----" << endl; bind (hello, placeholders::_1)("hello bind 2!" ); cout << bind (sum, placeholders::_1, placeholders::_2)(200 , 300 ) << endl; cout << "----- function -----" << endl; function<void (string)> func1 = bind (hello, _1); func1 ("hello function 1!" ); func1 ("hello function 2!" ); func1 ("hello function 3!" ); return 0 ; }
在语句 bind(hello, "hello bind!")();
中,bind 将 “hello bind 1!” 绑定至 hello 的 string 类型参数,并返回一个函数对象,调用这个函数对象的operator()
函数,完成打印字符串的过程。
在语句 bind(hello, _1)("hello bind 2!");
中的 _1
是名称空间 placeholders 中的,用法 placeholder::_1
。此为参数占位符 ,代表 hello 的第一个参数等待用户输入。在本例中将参数 “hello bind 2!” 传递给 operator()
函数完成调用。
用 function 实现对 bind 绑定的函数对象的类型保留
bind 有个缺点:bind 无法保存它所绑定过的函数对象!
所以就需要 function 和它进行配合。
placeholders 占位符:最多支持20个
1. 线程池模拟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <iostream> #include <typeinfo> #include <string> #include <memory> #include <vector> #include <functional> #include <thread> using namespace std;using namespace placeholders;class Thread {public : Thread (function<void (int )> func, int no) : _func(func) , _no(no) { } thread start () { thread t (_func, _no) ; return t; }private : function<void (int )> _func; int _no; };class ThreadPool {public : ThreadPool () {} ~ThreadPool () { for (int i = 0 ; i < _pool.size (); ++i) { delete _pool[i]; } } void startPool (int size) { for (int i = 0 ; i < size; ++i) { _pool.push_back ( new Thread (bind (&ThreadPool::runInThread, this , _1), i) ); } for (int i = 0 ; i < size; ++i) { _handler.push_back (_pool[i]->start ()); } for (thread& t : _handler) { t.join (); } }private : vector<Thread*> _pool; vector<thread> _handler; void runInThread (int id) { cout << "call runInThread! id: " << id << endl; } };int main () { ThreadPool pool; pool.startPool (10 ); return 0 ; }
4. lambda 表达式 lambda这个词起源于数学上的λ,在C++中利用lambda表达式,可以方便的定义和创建匿名函数。lambda可以看做函数对象的升级版 。改进了函数对象以下的缺点:
使用在泛型算法中的参数传递
比较性质/自定义操作
优先级队列
智能指针
1. 表达式语法
1 [捕获外部变量](形参列表)->返回值{操作代码};
如果 lambda 不需要返回值,那么返回值可以省略。也就是这样:
参数列表 :与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
mutable :默认情况下,lambda函数总是一个const函数 ,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略
-> 返回值类型 :与普通函数返回值一样表达函数的返回值类型,可以省略,译器会对返回值类型进行推导。
函数体 :可以使用参数以及捕获到的变量。
捕获外部变量方式:
[]
:表示不捕获任何外部变量
[=]
:捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
[&]
:捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
[this]
:捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
[=,&a]
:以传值的方式捕获外部的所有变量,但是a变量以传引用的方式捕获
[a,b]
:以传值的方式捕获外部变量 a 和 b
[a,&b]
:a 以传值方式捕获,b 以传引用的方式捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std;int main () { [] {}; int a = 3 , b = 4 ; [=] { return a + b; }; auto func1 = [&](int c) { return b = a + c; }; func1 (10 ); auto func2 = [=](int c) mutable { return b = a + c; }; func2 (10 ); return 0 ; }
2. lambda 底层原理 - 函数对象
1 2 auto func1 = [](){cout << "hello world!" << endl; };func1 ();
其对应的类:
1 2 3 4 5 6 7 8 9 10 template <typename T = void >class TestLambda01 {public : TestLambda01 () {} void operator ()()const { cout << "hello world!" << endl; } };
1 2 auto func2 = [](int a, int b)->int { return a + b; }; cout << func2 (20 , 30 ) << endl;
其对应的类:
1 2 3 4 5 6 7 8 9 10 template <typename T = int >class TestLambda02 {public : TestLambda02 () {} int operator ()(int a, int b) const { return a + b; } };
1 2 3 4 5 6 7 8 9 10 int a = 10 ;int b = 20 ;auto func3 = [&]() { int tmp = a; a = b; b = tmp; };func3 ();
其对应的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <typename T = int >class TestLambda03 {public : TestLambda03 (int &a, int &b):ma (a), mb (b) {} void operator ()() const { int tmp = ma; ma = mb; mb = tmp; }private : int &ma; int &mb; };
1 2 3 4 5 6 7 auto func4 = [=]() mutable { int tmp = a; a = b; b = tmp; };func4 ();
其对应的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 template <typename T = int >class TestLambda04 {public : TestLambda04 (int a, int b) :ma (a), mb (b) { } void operator ()() const { int tmp = ma; ma = mb; mb = tmp; }private : mutable int ma; mutable int mb; };
总结:
lambda 中的捕获参数列表对应类中成员变量的类型,返回值和参数列表对应 operator()
的返回值和参数列表。
lambda 表达式在编译期间被编译器自动转换成函数对象执行,调研 operator
3. lambda 表达式代替 switch 1 2 3 4 5 6 7 8 9 10 map<int , function<int (int , int )>> caculateMap; caculateMap[1 ] = [](int a, int b)->int {return a + b; }; caculateMap[2 ] = [](int a, int b)->int {return a - b; }; caculateMap[3 ] = [](int a, int b)->int {return a * b; }; caculateMap[4 ] = [](int a, int b)->int {return a / b; }; cout << "选择:" ;int choice; cin >> choice; cout << "10 op 15:" << caculateMap[choice](10 , 15 ) << endl;
4. 按值捕获 & 捕获时机 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <string> using namespace std;int main () { string str = "abc" ; auto localStr = [=](string& s) { s = str; }; str = "hello world" ; string str1; localStr (str1); cout << str; return 0 ; }
str = "abc"
当闭包生成的那一刻,被捕获的变量已经按值赋值的方式进行了捕获,后面那个str
再怎么变化,已经和闭包对象里面的值没有关系了.
localStr 中 str 中的值在localStr 定义的时候就已经确定为abc了,不会再发生变化。
5. 按引用捕获 & 悬空引用 在 C++ 编程中,程序员有责任保证 Lambda 调用的时候,保证被捕获的变量仍然有效~!是的,责任在你,而不在编译器。如果不能很好理解这点,就会遇到悬空引用的问题!
悬空引用( dangling references ) :就是说当创建了一个对象的引用类型的变量,但是被引用的对象被析构了、无效了。一般情况下,引用类型的变量必须在初始化的时候赋值,很少遇到这种情况,但是如果 lambda 被延迟调用,在调用时,已经脱离了当前的作用域,那么按引用捕获的对象就是悬空引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <string> #include <functional> using namespace std; std::function<void (void )> func; void test () { std::string str = "abc" ; func = [&]() { std::string a = str; cout << a; }; } int main () { test (); func (); return 0 ; }
发生 crash:func 引用的对象 str 在离开作用域 test()
后被析构了,在 main 函数中执行 func()
导致找到引用的对象 str,导致 crash。
6. lambda 实现指针自定义删除器 1 2 3 unique_ptr<FILE, function<void (FILE*)>> ptr1 (fopen ("data.txt" , "w" ), [](FILE *pf) {fclose (pf); });
7. lambda实现多种比较操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <functional> #include <vector> #include <queue> using namespace std;class Data {public : Data (int val1 = 10 , int val2 = 10 ) :ma (val1), mb (val2) {} bool operator >(const Data &data) const { return ma > data.ma; } bool operator <(const Data &data) const { return ma < data.ma; } int ma; int mb; };int main () { using FUNC = function<bool (Data&, Data&)>; priority_queue< Data, vector<Data>, FUNC > maxHeap ([](Data& d1, Data& d2)->bool { return d1.mb > d2.mb; }); maxHeap.push (Data (10 , 20 )); maxHeap.push (Data (15 , 15 )); maxHeap.push (Data (20 , 10 )); }