现在的位置: 首页 > 综合 > 正文

新C++标准:C++0x教程(三):面向所有开发者的特性(中)

2013年08月05日 ⁄ 综合 ⁄ 共 7685字 ⁄ 字号 评论关闭
文章目录

译者:yurunsun@gmail.com
新浪微博@孙雨润
新浪博客
CSDN博客
日期:2012年11月14日

原作:Scott Meyers

这些是Scott Meyers培训教程《新C++标准:C++0x教程》的官方笔记,培训课程的描述请见
http://www.aristeia.com/C++0x.html
,版权信息请见

http://aristeia.com/Licensing/licensing.html.

漏洞和建议请发送邮件到 smeyers@aristeia.com. 翻译错误请发送邮件给yurunsun@gmail.com (译者注).

7. lambda表达式

7.1 lambda能在需要用的时候快速创建函数对象

std::vector<int> v;
…
auto it = std::find_if(v.cbegin(), v.cend(),
[](int i) { return i > 0 && i < 10; });

相当于c++98中的:

class MagicType1 {
public:
    bool operator()(int i) const { return i > 0 && i < 10; }
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType1());

再举一个例子:

typedef std::shared_ptr<Widget> SPWidget;
std::deque<SPWidget> d;
…
std::sort(d.begin(), d.end(), [](const SPWidget& sp1, const SPWidget& sp2) { return *sp1 < *sp2; });

相当于c++98中的:

class MagicType2 {
public:
    bool operator()(const SPWidget& p1, const SPWidget& p2) const { return *p1 < *p2; }
};
std::sort(d.begin(), d.end(), MagicType2());

7.2 lambda函数中的变量

std::function<bool(int)> returnLambda(int a) { // return type to be discussed soon
    int b, c;
    ...
    // 编译不通过,报错 not captured
    return [](int x) { return a*x*x + b*x + c == 0; }; 
}
auto f = returnLambda(10); // f is essentially a
// copy of λ’s closure
if (f(22)) ... // “invoke the lambda”

使用静态变量可以解决这个问题:

#include <iostream>
#include <functional>
using namespace std;

int a = 1;
function<bool(int)> returnLambda() {
    static int b, c;
    return [](int x) {
        return a*x*x + b*x + c == 0;
    };
}

int main()
{
    cout << "Hello World!" << endl;
    auto f = returnLambda();
    if(f(22)) {
        cout << " great! " << endl;
    };
    return 0;
}

总结一些lambda表达式中针对变量的规则:

7.2.1 调用上下文中的局部变量必须能被捕捉(captured)

std::function<bool(int)> returnLambda(int a) {
    int b, c;
    ...
    return [](int x){ return a*x*x + b*x + c == 0; }; 
} // 编译器需要capture a, b, c; 因此无法编译通过

7.2.2 没有使用局部变量的lambda总是合法的:

int a;
std::function<bool(int)> returnLambda() {
    static int b, c;
    ...
    return [](int x){ return a*x*x + b*x + c == 0; };
} // 不需要capture a, b, c

7.2.3 使用变量可通过传值或传引用的方式

{
    int minVal;
    double maxVal;
    …
    auto it = std::find_if(v.cbegin(), v.cend(),
        [minVal, maxVal](int i) { return i > minVal && i < maxVal; });
}
{
    int minVal;
    double maxVal;
    …
    auto it = std::find_if( v.cbegin(), v.cend(),
        [&minVal, &maxVal](int i){ return i > minVal && i < maxVal; });
}

相当于C++98中的:

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

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

7.2.4 默认Capture方式可以指定

auto it = std::find_if(v.cbegin(), v.cend(), {return i > minVal && i < maxVal;}); // 默认传值
auto it = std::find_if(v.cbegin(), v.cend(), [&](int i) {return i > minVal && i < maxVal;}); // 默认传引用

如果提供了上述默认Capture方式,变量(例如minVal maxVal)可以不必列出。

显示指定某个变量的Capture方式,能够覆盖默认Capture方式:

// 默认传值,对于maxVal变量传引用
auto it = std::find_if(v.cbegin(), v.cend(), [=, &maxVal](int i){ return i > minVal && i < maxVal; });

相当于C++98中的:

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

7.2.5 如何去Capture类的成员变量

可以通过capture this的方式达到访问成员变量的目的:

class Widget {
public:
    void doSomething();
private:
    std::list<int> li;
    int minVal;
};
void Widget::doSomething() {
    auto it = std::find_if(li.cbegin(), li.cend(), [minVal](int i) { return i > minVal; }); // error!
    …
}
void Widget::doSomething() {
    auto it = std::find_if(li.cbegin(), li.cend(), [this](int i) { return i > minVal; }); // fine
    …
}

上边例子中lambda被用在成员函数中声明了一个函数对象,范围是类的内部,因此不需要friend修饰,lambda能够访问类的所有成员。

this的capture方式同样可以指定传值还是传引用:

class Widget {
public:
    void doSomething();
private:
    std::list<int> li;
    int minVal;
};
void Widget::doSomething() {
    auto it = std::find_if(li.cbegin(), li.cend(), [=](int i) { return i > minVal; });
    …
}
void Widget::doSomething() {
    auto it = std::find_if(li.cbegin(), li.cend(), [&](int i) { return i > minVal; });
    …
}

对于this指针,传值要比传引用高效,因为传引用导致二次寻址,虽然这属于编译器优化的能力范围之内。

7.3 lambda返回值

有以下几个选项:

  • 返回void
  • lambda的表达式是return expression形式,返回类型为expression的类型
  • 通过trailing return type语法来指定

最后一点中的trailing return type意思是只有指定实参之后,才能确定它返回什么类型,在一开始的C++0x草案中被称为“late-specified return type”。trailing return type更详细的规则是:

  • 必须用在lambda表达式中用于指定返回值
  • 经常与decltype一起使用
  • auto配合使用可以用于任何函数

    
    void f(int x); // traditional syntaxauto 
    f(int x)->void; // same declaration with trailing return type
    
    class Widget {
    public: 
        void mf1(int x); // traditional 
        auto mf2() const -> bool; // trailing return type};
    

7.4 无参数的形式的lambda会忽略参数列表

void doWork(int x, int y);
void doMoreWork();
std::thread t1([]() { doWork(10, 20); doMoreWork(); }); // w/empty param list
std::thread t2([] { doWork(10, 20); doMoreWork(); }); // w/o empty param list

7.5 复杂的lambda表达式

除了无法直接使用this指针外,lambda表达式中可以和任何函数一样复杂;从可维护的角度考虑,短小清晰、与上下文相关的lambda比较合适。

7.6 保存lambda闭包(表达式)

闭包类型的作用域是包含此lambda的最小{}/class/namespace

7.6.1 auto

auto multipleOf5 = [](long x) { return x % 5 == 0; };
std::vector<long> vl;
…
vl.erase(std::remove_if(vl.begin(), vl.end(), multipleOf5), vl.end());

7.6.2 std::function

std::function<bool(long)> multipleOf5 = // see next page for syntax
[](long x) { return x % 5 == 0; };
…
vl.erase(std::remove_if(vl.begin(), vl.end(), multipleOf5), vl.end());

lambda不能直接递归,但是可以借助std::function完成递归的效果:

std::function<int(int)> factorial = [&](int x) { return (x==1) ? 1 : (x * factorial(x-1)); };

7.6.3 函数类型

一个函数的类型可以通过声明确定:

void f1(int x);                     // type is void(int)
double f2(int x, std::string& s);   // type is double(int, std::string&)

std::function的类型表示法:

std::function<bool(long)> multipleOf5 = [](long x) { return x % 5 == 0; };  // from prior slide

或者使用trailing return type

std::function<auto (long)->bool> multipleOf5 = [](long x) { return x % 5 == 0; }; 

7.6.4 autostd::function的比较

auto更加简洁,但是有些时候auto无法代替std::function

void useIt(auto func);                      // error!
void useIt(std::function<bool(long)> func); // fine
template<typename Func>
void useIt(Func func);                      // fine, but generates  multiple functions

auto makeFunc();                            // error!
std::function<bool(long)> makeFunc();       // fine
template<typename Func>
Func makeFunc(); // fine, but generates multiple functions, and callers must specify Func

auto不允许作为类的成员变量:

class Widget {
private:
    auto func;                       // error!
    ...
};
class Widget {
private:
    std::function<bool(long)> f;    // fine
    ...
};

至于autostd::function效率比较,Stephan T. Lavavej说“编译器必须表现的足够勇武,才能使std::function变得和auto效率相近。

7.6.5 保存的lambda闭包可能会导致野指针/悬空引用

也就是已经被删除的堆上的对象指针,或者超过作用域的对象的引用。

std::vector<long> vl;
std::function<bool(long)> f;
{
    int divisor;     some block
    …
    f = [&](long x) { return x % divisor == 0; }; // closure refers to local var
} // local var’s
// scope ends
vl.erase(std::remove_if(vl.begin(), vl.end(), f), vl.end());// calls to f use dangling ref!

7.6.6 保存的lambda作为容器比较函数

auto cmpFnc = [](int *pa, int *pb) { return *pa < *pb; };   // compare values, not pointers
std::set<int*, decltype(cmpFnc)> s(cmpFnc);     // sort s that way

闭包不会自动构造,因此下边的语法编译报错:

std::set<int*, decltype(cmpFnc)> s; // error! comparison object can't be  constructed

7.7 lambda与闭包的总结

  • lambda产生闭包
  • 调用使用的变量可以以传值或者传引用的方式被capture
  • 使用trailing return type的方式指定返回值
  • 可以使用autostd::function来存储闭包,要当心悬空引用和野指针
  • 推荐撰写必要的简洁清晰的lambda表达式

8. 模板别名

8.1 使用using可以声明模板别名。

template<typename T>
using MyAllocVec = std::vector<T, MyAllocator>;
MyAllocVec<int> v;                  // std::vector<int, MyAllocator>
template<std::size_t N>
using StringArray = std::array<std::string, N>;
StringArray<15> sa;                 // std::array<std::string, 15>
template<typename K, typename V>
using MapGT = std::map<K, V, std::greater<K>>;
// std::map<long long, std::shared_ptr<std::string>, std::greater<long long>>
MapGT<long long, std::shared_ptr<std::string>>  myMap;          

8.2 模板别名不能被特化:

template<typename T> 
using MyAllocVec = std::vector<T, MyAllocator>;     // fine
template<typename T>
using MyAllocVec = std::vector<T*, MyPtrAllocator>; // error!

可以使用trait class达到特化的目的:

template<typename T>                                // primary template
struct VecAllocator {
    typedef MyAllocator type;
};
template<typename T>                                // specialized template
struct VecAllocator<T*> {
    typedef MyPtrAllocator type;
};
template<typename T>
using MyAllocVec = std::vector<T, typename VecAllocator<T>::type>;

8.3 using能做typedef能做的事情,而且可读性更好:

typedef std::unordered_set<int> IntHash; // these 2 lines do the same thing
using IntHash = std::unordered_set<int>;  

typedef void (*CallBackPtr)(int); // func. ptr. typedef equivalent using decl.
using CallBackPtr = void (*)(int); 

抱歉!评论已关闭.