C++避坑指南


 640?wx_fmt=gif




导语:若是,将编程语言比做武功秘籍,C++无异于《九阴真经》。《九阴真经》威力强大、博大精深,经中所载内功、轻功、拳、掌、腿、刀法、剑法、杖法、鞭法、指爪、点穴密技、疗伤法门、闭气神功、移魂大法等等,无所不包,C++亦如是。c++


C++跟《九阴真经》同样,若是使用不当,很容易落得跟周芷若、欧阳锋、梅超风等同样走火入魔。这篇文章总结了在学习C++过程当中容易走火入魔的一些知识点。为了不篇幅浪费,太常见的误区(如指针和数组、重载、覆盖、隐藏等)在本文没有列出,文中的知识点也没有先后依赖关系,各个知识点基本是互相独立,并无作什么铺垫,开门见山直接进入正文。编程


目录设计模式

1 函数声明和对象定义数组

2 静态对象初始化顺序安全

3 类型转换闭包

3.1 隐式转换并发

3.2 显示转换app

4 inline内联编程语言

5 名称查找分布式

5.1 受限名称查找

5.2 非受限名称查找

6 智能指针

6.1 std::auto_ptr

6.2 std::shared_ptr

6.3 std::unique_ptr

7 lambda表达式


1 函数声明和对象定义

对象定义写成空的初始化列表时,会被解析成一个函数声明。能够采用代码中的几种方法定义一个对象。

 
 
//这是一个函数声明	
//不是一个对象定义	
string foo();	
	
	
//函数声明	
string foo(void);	
	
	
//对象定义几种方法	
string foo;	
string foo{ };//c++11	
string *foo = new string;	
string *foo = new string();	
string *foo = new string{ };//c++11

(左滑能够查看所有代码,下同)


2 静态对象初始化顺序

在同一个编译单元中,静态对象的初始化次序与其定义顺序保持一致。对于做用域为多个编译单元的静态对象,不能保证其初始化次序。以下代码中,在x.cpp和y.cpp分别定义了变量x和y,而且双方互相依赖。

 
 
//x.cpp	
extern int y;	
int x = y + 1;

x.cpp中使用变量y来初始化x


 
 
//y.cpp	
extern int x;	
int y = x + 1;

y.cpp中使变量x来初始化y

 
 
//main.cpp	
extern int x;	
extern int y;	
int main()	
{	
    cout << "x = " << x << endl;	
    cout << "y = " << y << endl;	
    return 0;	
}


若是初始化顺序不同,两次执行的结果输出不同,以下所示:

 
 
g++ main.cpp x.cpp y.cpp	
./a.out 	
x = 1	
y = 2


 
 
g++ main.cpp y.cpp x.cpp	
./a.out 	
x = 2	
y = 1


若是咱们须要指定依赖关系,好比y依赖x进行初始化,能够利用这样一个特性来实现:函数内部的静态对象在函数第一次调用时初始化,且只被初始化一次。使用该方法,访问静态对象的惟一途径就是调用该函数。改写后代码以下所示:

 
 
//x.h	
extern int &getX();	
//x.cpp	
int &getX()	
{	
    static int x;	
    return x;	
}

getX()函数返回x对象


 
 
//y.h	
extern int &getY();	
//y.cpp	
#include "x.h"	
int &getY()	
{	
    static int y = getX() + 1;	
    return y;	
}

y对象使用x对象进行初始化


 
 
//main.cpp	
int main()	
{	
    cout << "x = " << getX() << endl;	
    cout << "y = " << getY() << endl;	
    return 0;	
}

打印x和y值。经过这种方式,就保证了x和y的初始化顺序。


 
 
g++ main.cpp x.cpp y.cpp	
./a.out 	
x = 0	
y = 1


 
 
g++ main.cpp y.cpp x.cpp	
./a.out 	
x = 0	
y = 1


3 类型转换


这里只描述自定义类的类型转换,不涉及如算数运算的类型自动提高等。


3.1 隐式转换

C++自定义类型在如下两种状况会发生隐式转换:

1) 类构造函数只有一个参数或除第一个参数外其余参数有默认值;
2) 类实现了operator type()函数;

 
 
class Integer	
{	
public:	
    Integer() : m_value(0) {  }	
    //int --> Integer	
    Integer(int value) : m_value(value)	
    {	
        cout << "Integer(int)" << endl;	
    }	
	
	
    //Integer --> int	
    operator int()	
    {	
        cout << "operator int()" << endl;	
        return m_value;	
     }	
    	
private:	
    int m_value;	
};


上面定义了一个Integer类,Integer(int)构造函数能够将int隐式转换为Integer类型。operator int()函数能够将Integer类型隐式转换为int。从下面代码和输出中能够看出确实发生了隐式的类型转换。

 
 
int main()	
{	
    Integer value1;	
    value1 = 10;	
    cout << "value1=" << value1 << endl;	
    cout << "*******************" << endl;	
    int value2 = value1;	
    cout << "value2=" << value2 << endl;	
    return 0;	
}


 
 
output:	
Integer(int)	
operator int()	
value1=10	
*******************	
operator int()	
value2=10

隐式类型转换在某些场景中确实比较方便,如:


a、运算符重载中的转换,如能够方便的使Integer类型和内置int类型进行运算

 
 
const Integer operator+(const Ingeter &lhs, const Ingeter &rhs)	
{	
    return Integer(lhs.m_value + rhs.m_value);	
}	
Integer value = 10;	
Integer sum = value + 20;


b、条件和逻辑运算符中的转换,如可使智能指针像原生裸指针同样进行条件判断

 
 
template<typename T>	
class AutoPtr	
{	
public:	
    operator bool() const { return m_ptr; }	
private:	
    T *m_ptr;	
};	
AutoPtr<int> ptr(new int(10));	
if(ptr)	
{	
    //do something	
}


隐式类型转换在带来便利性的同时也带来了一些坑,以下所示:

 
 
template<typename T>	
class Array	
{	
public:	
    Array(int size);	
    const T &operator[] (int index);	
    friend bool operator==(const Array<T> &lhs, const Array<T> &rhs);	
};	
Array<int> arr1(10);	
Array<int> arr2(10);	
if(arr1 == arr2[0])	
{	
    //do something	
}


构造函数隐式转换带来的坑。上述代码定义了一个Array类,并重载了operator==运算符。本意是想比较两个数组,可是if(arr1 == arr2)误写成了f(arr1 == arr2[0]),编译器不会抱怨,arr2[0]会转换成一个临时Array对象而后进行比较。

 
 
class String	
{	
public:	
    String(const char *str);	
    operator const char* () const{ return m_data; }	
private:	
    char *m_data;	
};	
const char *strcat(const char *str1, const char *str2)	
{	
    String str(str1);	
    str.append(str2);	
    return str;	
}


operator type()带来的坑。上述String类存在到const char *的隐式转换,strcat函数返回时String隐身转换成const char *,而String对象已经被销毁,返回的const char *指向无效的内存区域。这也是std::string不提提供const char *隐式转换而专门提供了c_str()函数显示转换的缘由。


3.2 显示转换

正是因为隐式转换存在的坑,C++提供explicit关键字来阻止隐式转换,只能进行显示转换,分别做用域构造函数和operator(),以下所示:

1) explicit Ctor(const type &);
2) explicit operator type();

 
 
class Integer	
{	
public:	
    Integer() : m_value(0) {  }	
    //int --> Integer	
    explicit Integer(int value) : m_value(value)	
    {	
        cout << "Integer(int)" << endl;	
    }	
    //Integer --> int	
    explicit operator int()	
    {	
        cout << "operator int()" << endl;	
        return m_value;	
    }	
private:	
    int m_value;	
};


用explicit改写Integer类后,须要进行显示转换才能与int进行运算,以下:

 
 
int main()	
{	
    Integer value1;	
    //value1 = 10;  //compile error	
    value1 = static_cast<Integer>(10);	
    cout << "value1=" << (int)value1 << endl;	
	
	
    //int value2 = value1; //compile error	
    int value2 = static_cast<int>(value1);	
    cout << "value2=" << value2 << endl;	
    return 0;	
}


为了保持易用性,C++11中explicit operator type()在条件运算中,能够进行隐式转换,这就是为何C++中的智能指针如shared_ptr的operator bool()加了explicit还能直接进行条件判断的缘由。下面代码来自shared_ptr源码。

 
 
explicit operator bool() const _NOEXCEPT	
{ // test if shared_ptr object owns a resource	
return (get() != nullptr);	
}


4 inline内联

内联相似于宏定义,在调用处直接展开被调函数,以此来代替函数调用,在消除宏定义的缺点的同时又保留了其优势。内联有如下几个主要特色:

a、内联能够发生在任什么时候机,包括编译期、连接期、运行时等;


b、编译器很无情,即便你加了inline,它也可能拒绝你的inline;


c、编译器不少情,即便你没有加inline,它也可能帮你实施inline;


d、不合理的inline会致使代码臃肿。


使用内联时,须要注意如下几个方面的误区:

1)inline函数需显示定义,不能仅在声明时使用inline。类内实现的成员函数是inline的。

 
 
inline int add(int, int);	
	
	
int add(int x, int y) //no inline	
{	
    return x + y;	
}	
class Calculator	
{	
public:	
    static int add(int x, int y) //inline	
    {	
        return x + y;	
     }	
    static int sub(int x, int y);	
};	
int Calculator::sub(int x, int y) //no inline	
{	
    return x - y;	
}


2)经过函数指针对inline函数进行调用时,编译器有可能不实施inline

 
 
inline int add(int x, int y)	
{	
    return x + y;	
}	
//定义函数指针	
int (*pfun)(int, int) = add;	
	
	
add(3, 5); //实施inline	
pfun(3, 5); //经过函数指针调用,可能没法inline


3)编译器可能会拒绝内联虚函数,但能够静态肯定的虚函数对象,多数编译器能够inline

 
 
class Animal	
{	
public:	
    virtual void walk() = 0;	
};	
clas Penguin : public Animal	
{	
public:	
    virtual void walk(){ }	
}	
Animal *p1 = new Penguin();	
p1->walk(); //大多数编译器没法inline	
	
	
Penguin *p2 = new Penguin();	
p2->walk(); //大多数编译器能够inline


4)inline函数有局部静态变量时,可能没法内联

 
 
inline void report(int code)	
{	
    static int counter = 0;	
    doReport(code, ++counter);	
}	
report(-9998);//可能没法inline


5)直接递归没法inline,应转换成迭代或者尾递归。下面分别以递归和迭代实现了二分查找。

 
 
template<typename T>	
inline int recursionSearch(const vector<T> &vec, const T &val, int low, int high)	
{	
    if(low > high) return -1;	
    int mid = (low + high) / 2;	
    if(val < vec[mid])	
    {	
        return recursionSearch(vec, val, low, mid - 1);	
    }	
    else if(val > vec[mid])	
    {	
        return recursionSearch(vec, val, mid + 1, high);	
    }	
    else	
    {	
        return mid;	
    }	
}

二分查找的递归方式实现。


 
 
template<typename T>	
inline int iterationSearch(const vector<T> &vec, const T &val)	
{	
    int low = 0;	
    int high = vec.size() - 1;	
    while(low <= high)	
    {	
        int mid = (low + high) / 2;	
        if(val < vec[mid])	
        {	
            high = mid -1;	
        }	
        else if(val > vec[mid])	
        {	
            low = mid +1;	
        }	
        else	
        {	
            return mid;	
        }	
    }	
    return -1;	
}

二分查找的迭代方式实现。


640


分别调用二分查找的递归和迭代实现,开启-O1优化,经过查看汇编代码和nm查看可执行文件可执行文件符号,只看到了递归版本的call指令和函数名符号,说明递归版本没有内联,而迭代版本实施了内联展开。

640?wx_fmt=png


6)构造函数和析构函数可能没法inline,即便函数体很简单

 
 
class Student : public Person	
{	
public:	
    Student() {  };	
    virtual ~Student() {  }	
private:	
    School m_school;	
};	
	
	
Student::Student()	
{	
    try	
    {	
        Person::Person(); //构造基类成分	
    }	
    catch(...)	
    {	
        throw;	
    }	
    try	
    {	
        m_school.School::School();//构造m_school;	
    }	
    catch(...)	
    {	
        Person::~Person();	
        throw;	
    }	
}


表面上构造函数定义为空且是inline,但编译器实际会生成如右侧的伪代码来构造基类成分和成员变量,从而不必定能实施inline。


5 名称查找

C++中名称主要分为如下几类:

a) 受限型名称:使用做用域运算符(::)或成员访问运算符(.和->)修饰的名称。
如:::std、std::sort、penguin.name、this->foo等。


b) 
非受限型名称:除了受限型名称以外的名称。
如:name、foo


c) 依赖型名称:依赖于形参的名称。
如:vector<T>::iterator


d) 
非依赖型名称:不属于依赖型名称的名称。
如:vector<int>::iterator


5.1 受限名称查找

受限名称查找是在一个受限做用域进行的,查找做用域由限定的构造对象决定,若是查找做用域是类,则查找范围能够到达基类。

 
 
class B	
{	
public:	
    int m_i;	
};	
class D : public B	
{	
}	
void foo(D *pd)	
{	
    pd->m_i = 0; //查找做用域到达基类B,即 B::m_i;	
}


5.2 非受限名称查找

5.2.1 普通查找:由内向外逐层查找,存在继承体系时,先查找该类,而后查找基类做用域,最后才逐层查找外围做用域

 
 
extern string name;  //(1)	
string getName(const string &name) //(2)	
{	
    if(name.empty())	
    {	
        string name = "DefaultName"; //(3)	
        return getName(name); //引用name(3)	
    }	
    return name/*引用name(2)*/ + ::name/*引用name(1)*/;	
}


5.2.2 ADL(argument-dependent lookup)查找:又称koenig查找,由C++标准委员会Andrew Koenig定义了该规则——若是名称后面的括号里提供了一个或多个类类型的实参,那么在名称查找时,ADL将会查找实参关联的类和命名空间。

 
 
namespace ns	
{	
    class C{  };	
    void foo(const C &c)	
    { 	
        cout << "foo(const C &)" << endl;	
     }	
}	
int main()	
{	
    ns::C c;	
    foo(c);	
    return 0;	
}

根据类型C的实参c,ADL查找到C的命名空间ns,找到了foo的定义。


了解了ADL,如今来看个例子,下面代码定义了一个Integer类和重载了operator<运算符,并进行一个序列排序。

 
 
namespace ns	
{	
    class Integer	
    {	
     public:	
        explicit Integer(int value) : m_value(value){  }	
        int m_value = 0;	
     };	
}	
bool operator<(const Integer &lhs, const Integer &rhs)	
{	
    return lhs.m_value < rhs.m_value;	
}	
int main()	
{	
    using ns::Integer;	
    std::vector<Integer> v = {Integer(1), Integer(5), Integer(1), Integer(10)};	
    std::sort(v.begin(), v.end());	
    for(auto const &item : v)	
    {	
        std::cout << item.m_value << " ";	
    }	
    std::cout << std::endl;	
    return 0;	
}


上面的代码输出什么? 1 1 5 10吗。上面的代码没法编译经过,提示以下错误

 
 
/usr/include/c++/4.8.2/bits/stl_heap.h:235:35: 	
错误:no match for ‘operator<’ 	
(operand types are ‘ns::Integer’ and ‘ns::Integer’)  	
if (*(__first + __secondChild) < *(__first + (__secondChild - 1)))


operator<明明在全局做用于有定义,为何找不到匹配的函数?前面的代码片断,应用ADL在ns内找不到自定义的operator<的定义,接着编译器从最近的做用域std内开始向外查找,编译器在std内找到了operator<的定义,因而中止查找。定义域全局做用域的operator<被隐藏了,即名字隐藏。名字隐藏一样能够发生在基类和子类中。好的实践:定义一个类时,应当将其相关的接口(包括自由函数)也放入到与类相同的命名空间中

 
 
namespace ns	
{	
    class Integer	
    {	
    public:	
        explicit Integer(int value) : m_value(value){  }	
	
	
        int m_value = 0;	
    };	
    bool operator<(const Integer &lhs, const Integer &rhs)	
    {	
        return lhs.m_value < rhs.m_value;	
    }	
}


把operator<定义移到ns命名空间后运行结果正常

640?wx_fmt=png


再来看一个名称查找的例子。

 
 
template<typename U>	
struct B	
{	
    typedef int T;	
};	
template<typename T>	
struct D1 : B<int>	
{	
    T m_value;	
};	
int main()	
{	
    int value = 10;	
    D1<int *> d1;	
    d1.m_value = &value;	
    cout << *d1.m_value << endl;	
    return 0;	
}

这段代码编译时提示以下错误,咱们用int *实例化D1的模板参数并给m_value赋值,编译器提示没法将int *转换成int类型,也就是m_value被实例化成了int而不是int *。


640


咱们将代码改动一下,将D2继承B<int>改成B<T>,代码能够顺利编译并输出。

 
 
template<typename U>	
struct B	
{	
    typedef int T;	
};	
template<typename T>	
struct D2 : B<T>	
{	
    T m_value;	
};	
int main()	
{	
    int value = 10;	
    D2<int *> d2;	
    d2.m_value = &value;	
    cout << *d2.m_value << endl;	
	
	
    return 0;	
}


640?wx_fmt=png


D1和D2惟一的区别就是D1继承自B<int>,D2继承自B<T>。实例化后,为什么D1.m_value类型是int,而D2.m_value类型是int *。在分布式事务领域有二阶段提交,在并发编程设计模式中二阶段终止模式。在C++名称查找中也存在一个二阶段查找。


二阶段查找(two-phase lookup):首次看到模板定义的时候,进行第一次查找非依赖型名称。当实例化模板的时候,进行第二次查找依赖型名称。


D1中查找T时,基类B<int>是非依赖型名称,无需知道模板实参就肯定了T的类型。


D2中查找T时,基类B<T>是依赖型名称,在实例化的时候才会进行查找。

6 智能指针

6.1 std::auto_ptr

std::auto_ptr是C++98智能指针实现,复制auto_ptr时会转移全部权给目标对象,致使原对象会被修改,所以不具有真正的复制语义,不能将其放置到标准容器中。auto_ptr在c++11中已经被标明弃用,在c++17中被移除。

 
 
auto_ptr<string> ap1(new string("foo"));	
auto_ptr<string> ap2 = ap1;	
//内存访问错误,ap1管理的指针已经被置位空	
string str(*ap1);	
	
	
auto_ptr<string> ap3(new string("bar"));	
vector<auto_ptr<string>> ptrList;	
//ap2和ap3被复制进容器后其管理的指针对象为空	
//违反标准c++容器复制语义	
ptrList.push_back(ap2);	
ptrList.push_back(ap3);


6.2 std::shared_ptr

std::shared_ptr采用引用计数共享指针指向对象全部权的智能指针,支持复制语义。每次发生复制行为时会递增引用计数,当引用计数递减至0时其管理的对象资源会被释放。但shared_ptr也存在如下几个应用方面的陷阱。


1)勿经过传递裸指针构造share_ptr

 
 
{	
    string *strPtr = new string("dummy");	
    shared_ptr<string> sp1(strPtr);	
    shared_ptr<string> sp2(strPtr);	
}

这段代码经过一个裸指针构造了两个shared_ptr,这两个shared_ptr有着各自不一样的引用计数,致使原始指针被释放两次,引起未定义行为。


2)勿直接将this指针构造shared_ptr对象

 
 
class Object	
{	
public:	
    shared_ptr<Object> GetSelfPtr()	
    {	
        return shared_ptr<Object>(this);	
    }	
};	
shared_ptr<Object> sp1(new Object());	
shared_ptr<Object> sp2 = sp1->GetSelfPtr();


这段代码使用同一个this指针构造了两个没有关系的shared_ptr,在离开做用域时致使重复析构问题,和1)是一个道理。当但愿安全的将this指针托管到shared_ptr时,目标对象类须要继承std::enable_shared_from_this<T>模板类并使用其成员函数shared_from_this()来得到this指针的shared_ptr对象。以下所示:

 
 
class Object : public std::enable_shared_from_this<Object>	
{	
public:	
    shared_ptr<Object> GetSelfPtr()	
    {	
        return shared_from_this();	
    }	
};	
shared_ptr<Object> sp1(new Object());	
shared_ptr<Object> sp2 = sp1->GetSelfPtr();


3)请勿直接使用shared_ptr互相循环引用,如实在须要请将任意一方改成weak_ptr。

 
 
struct You;	
struct I	
{	
    shared_ptr<You> you;	
    ~I() { cout << "i jump" << endl; }	
};	
struct You	
{	
    shared_ptr<I> me;	
    ~You() { cout << "you jump" << endl; }	
};	
int main()	
{	
    shared_ptr<I> i(new I());	
    shared_ptr<You> you(new You());	
    i->you = you;	
    you->me = i;	
    return 0;	
}


640


代码运行结果,没有看到打印任何内容,析构函数没有被调用。最终你我都没有jump,完美的结局。可是现实就是这么残酷,C++的世界不容许他们不jump,须要将其中一个shared_ptr改成weak_ptr后资源才能正常释放。


 
 
struct You	
{	
    weak_ptr<I> me;//如今是weak_ptr	
    ~You() { cout << "you jump" << endl; }	
};	
int main()	
{	
    shared_ptr<I> i(new I());	
    shared_ptr<You> you(new You());	
    i->you = you;	
    you->me = i;	
    return 0;	
}


640?wx_fmt=png


4)优先使用make_shared而非直接构造shared_ptr。make_shared主要有如下几个优势:


a、可使用auto自动类型推导。

shared_ptr<Object> sp(new Object());

auto sp = make_shared<Object>();


 b、减小内存管理器调用次数。shared_ptr的内存结构以下图所示,包含了两个指针:一个指向其所指的对象,一个指向控制块内存。

640

 
 
shared_ptr<Object> sp(new Object());

 这条语句会调用两次内存管理器,一次用于建立Object对象,一次用于建立控制块。若是使用make_shared会一次性分配内存同时保存Object和控制块。


c、防止内存泄漏。

 
 
class Handler;	
string getData();	
int handle(shared_ptr<Handler> sp, const string &data);	
//调用handle	
handle(shared_ptr<Handler>(new Handler()), getData());


这段代码可能发生内存泄漏。通常状况下,这段代码的调用顺序以下:

new Handler()     ① 在堆上建立Handler对象

shared_ptr()        ②建立shared_ptr

getData()             ③调用getData()函数


可是编译器可能不按照上述①②③的顺序来生成调用代码。可能产生①③②的顺序,此时若是③getData()产生异常,而new Handler对象指针尚未托管到shared_ptr中,因而内存泄漏发生。使用make_shared能够避免这个问题。

 
 
handle(make_shared<Handler>(), getData());


这条语句在运行期,make_shared和getData确定有一个会先调用。若是make_shared先调用,在getData被调用前动态分配的Hander对象已经被安全的存储在返回的shared_ptr对象中,接着即便getData产生了异常shared_ptr析构函数也能正常释放Handler指针对象。若是getData先调用并产生了异常,make_shared则不会被调用。


可是make_shared并非万能的,如不能指定自定义删除器,此时能够先建立shared_ptr对象再传递到函数中。

 
 
shared_ptr<Handler> sp(new Handler());	
handle(sp, getData());


6.3 std::unique_ptr

std::unique_ptr是独占型智能指针,仅支持移动语义,不支持复制。默认状况下,unique_ptr有着几乎和裸指针同样的内存开销和指令开销,能够替代使用裸指针低开销的场景。


1)与shared_ptr不一样,unique_ptr能够直接指向一个数组,由于unique_ptr对T[]类型进行了特化。若是shared_ptr指向一个数组,须要显示指定删除器。

 
 
unique_ptr<T []> ptr(new T[10]);	
//显示指定数组删除器	
shared_ptr<T> ptr(new T[10], [](T *p){delete[] p;});


 2)与shared_ptr不一样,unique_ptr指定删除器时须要显示指定删除器的类型。

 
 
shared_ptr<FILE> pf(fopen("data.txt", "w"), ::fclose);	
//显示指定数组删除器类型	
unique_ptr<FILE, int(*)(FILE *)> pf(fopen("data.txt", "w"), ::fclose);	
unique_ptr<FILE, std::function<int(FILE *)>> pf(fopen("data.txt", "w"), ::fclose);


7 lambda表达式


1)捕获了变量的lambda表达式没法转换为函数指针。

 
 
using FunPtr = void(*)(int *);	
FunPtr ptr1 = [](int *p) { delete p; };	
FunPtr ptr2 = [&](int *p) { delete p; };//错误


2)对于按值捕获的变量,其值在捕获的时候就已经肯定了(被复制到lambda闭包中)。而对于按引用捕获的变量,其传递的值等于lamdba调用时的值。

 
 
int a = 10;	
auto valLambda = [a] { return a + 10; };	
auto refLambda = [&a] { return a + 10; };	
cout << "valLambda result:" << valLambda() << endl; //20	
cout << "refLambda result:" << refLambda() << endl; //20	
a += 10;	
cout << "valLambda result:" << valLambda() << endl; //20	
cout << "refLambda result:" << refLambda() << endl; //30


3)默认状况下,lambda没法修改按值捕获的变量。若是须要修改,须要使用mutable显示修饰。这其实也好理解,lambda会被编译器转换成operator() const的函数对象。

 
 
auto mutableLambda = [a]() mutable { return a += 10; };


4)lambda没法捕捉静态存储的变量。

 
 
static int a = 10;	
auto valLambda = [a] { return a + 10; }; //错误


640?wx_fmt=png