lambda表达式实际上是语法糖,任何lambda表达式能做到的,手动都能做到,无非是多打几个字。但是lambda作为一种创建函数对象的手段,实在太过方便,自从有了lambda表达式,使用复杂谓词来调用STL中的”_if”族算法(std::find_if,std::remove_if等)变得非常方便,这种情况同样发生在比较函数的算法族上。在标准库之外,lambda表达式可以临时制作出回调函数、接口适配函数或是语境相关函数的特化版本以供一次性调用。下面是关于lambda相关术语的提醒:

lambda表达式,是表达式的一种,比如下面代码中红色的就是lambda表达式:

std::find_if(container.begin(), container.end(),
[](int val) { return 0 < val && val < 10; });

闭包,是lambda表达式创建的运行期对象,在上面对std::find_if的调用中,闭包就是作为第三个实参在运行期传递给std::find_if的对象。

闭包类,是实例化闭包的类,每个lambda表达式都会触发编译器生成一个独一无二的闭包类,而lambda表达式中的语句会变成闭包类成员函数的可执行指令。

闭包可以复制,所以,对应于单独一个lambda表达式的闭包类型可以有多个闭包:

int x; // x is local variable
auto c1 = [x](int y) { return x * y > 55; }; // c1 is copy of the closure produced by the lambda
auto c2 = c1; // c2 is copy of c1
auto c3 = c2; // c3 is copy of c2
…

c1、c2和c3都是同一个lambda表达式产生的闭包的副本。

在非正式场合,lambda表达式,闭包和闭包类之间的界限可以模糊一些。但是在下面的条款中,需要能区别哪些存在于编译期(lambda表达式和闭包类),哪些存在于运行期(闭包),以及它们之间的相互联系。

31:避免默认捕获模式

C++11中有两种默认捕获模式:按引用或按值。按引用的默认捕获模式可能导致空悬引用,而按值的默认捕获模式可能会让你觉得不存在空悬引用的问题(实际上不是)。

按引用捕获会导致闭包包含指向局部变量(或形参)的引用,一旦由lambda表达式所创建的闭包的生命期超过了该局部变量或形参的生命期,那么闭包内的引用就会空悬,比如下面的代码:

using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters; // filtering funcsvoid addDivisorFilter()
{auto calc1 = computeSomeValue1();auto calc2 = computeSomeValue2();auto divisor = computeDivisor(calc1, calc2);filters.emplace_back([&](int value) { return value % divisor == 0; });
}

这段代码随时会出错,lambda中按引用捕获了局部变量divisor,但当addDivisorFilter函数返回时局部变量被销毁,使用filters就会产生未定义行为。

如果不这样做,使用显式方式按引用捕获divisor,问题依然存在:

filters.emplace_back([&divisor](int value) { return value % divisor == 0; }
);

但是通过显示捕获,就比较容易看出lambda表达式的生存依赖于divisor的生命期。显式的写出”divisor”可以提醒我们要保证divisor至少应该和lambda具有一样长的生命期,这要比[&]这种所传达的不痛不痒的“要保证没有空悬引用”式的劝告更让人印象深刻。

如果知道闭包会立即使用(比如传递给STL算法)且不会被复制,这种情况下,你可能会争论说,既然没有空悬引用的风险,也就没有必要避免使用默认引用捕获模式。但是从长远观点来看,显示的列出lambda表达式所依赖的局部变量或形参,是更好的软件工程实践。

上面的例子中,解决问题的一种办法是对divisor采用按值的默认捕获模式:

filters.emplace_back([=](int value) { return value % divisor == 0; }
);

对于这个例子而言,这样做确实是没问题的。但是按值的默认捕获并非一定能避免空悬引用,问题在于如果按值捕获了一个指针,在lambda表达式创建的闭包中持有的是这个指针的副本,但是没有办法阻止lambda表达式之外的代码针对该指针实施delete操作导致的指针副本空悬。比如下面的代码:

class Widget {
public:… // ctors, etc.void addFilter() const; // add an entry to filters
private:int divisor; // used in Widget's filter
};void Widget::addFilter() const {filters.emplace_back([=](int value) { return value % divisor == 0; });
}

这样的代码看起来安全,然而实际上却是大错特错的。捕获只能针对于在创建lambda表达式的作用域内可见的非静态局部变量(包括形参),而在Widget::addFilter函数体内,divisor并非局部变量,而是Widget类的成员变量,它根本没办法捕获。这么一来,如果不使用默认捕获模式,代码就不会通过编译:

void Widget::addFilter() const {filters.emplace_back([](int value) { return value % divisor == 0; });
}

而且,如果试图显示捕获divisor(无论是按值还是按引用),这个捕获语句都不能通过编译,因为divisor既不是局部变量,也不是形参:

void Widget::addFilter() const {filters.emplace_back([divisor](int value) { return value % divisor == 0; });
}

但是为什么一开始的代码没有发生编译错误呢?this指针是关键所在,每一个非静态成员函数都持有一个this指针,每当提及该类的成员变量时都会用到这个指针。比如在Widget的任何成员函数中,编译器内部都会把divisor替换成this->divisor。因此,在Widget::addFilter的按值默认捕获版本中,被捕获的实际上是Widget的this指针,而不是divisor。从编译器的角度来看,实际的代码相当于:

void Widget::addFilter() const {auto currentObjectPtr = this;filters.emplace_back([currentObjectPtr](int value) { return value % currentObjectPtr->divisor == 0; });
}

因此,该lambda闭包的存活,与它含有this指针指向的Widget对象的生命期是绑在一起的,比如下面的代码:

using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters; void doSomeWork() {auto pw = std::make_unique<Widget>(); pw->addFilter();…
}

当调用doSomeWork时创建了一个筛选函数,它依赖于std::make_unique创建的Widget对象,该函数被添加到filters中,然而当doSomeWork结束后,Widget对象随着std::unique_ptr的销毁而销毁,从那一刻起,filters中就含有了一个带有空悬指针的元素。

这一问题可以通过将想捕获的成员变量复制到局部变量中,而后捕获该局部变量的部分得意解决:

void Widget::addFilter() const {auto divisorCopy = divisor;filters.emplace_back([divisorCopy](int value) { return value % divisorCopy == 0; } );
}

在C++14中,捕获成员变量的一种更好的方式是使用广义lambda捕获(generalized lambda):

void Widget::addFilter() const {filters.emplace_back([divisor = divisor](int value) { return value % divisor == 0; });
}

对广义lambda捕获而言,没有默认捕获模式一说,但是,就算在C++14中,本条款的建议,避免使用默认捕获模式依然成立。

使用按值默认捕获的另一个缺点,在于它似乎表明闭包是自治的,与闭包外的数据变化绝缘,然而作为一般性的结论,这是不正确的。因为lambda表达式可能不仅依赖于局部变量或形参,他还可能依赖于静态存储期对象,这样的对象定义在全局或名字空间作用域中,或是在类,函数,文件中以static饰词声明。这样的对象可以在lambda内使用,但是它们不能被捕获。如果使用了按值默认捕获模式,这些对象就会给人以错觉,认为它们可以加以捕获:

void addDivisorFilter() {static auto calc1 = computeSomeValue1(); static auto calc2 = computeSomeValue2(); static auto divisor = computeDivisor(calc1, calc2);filters.emplace_back([=](int value) { return value % divisor == 0; });++divisor;
}

看到[=]就认为lambda复制了它内部使用的对象,得出lambda是自治的这种结论,是错误的。实际上该lambda表达式并不独立,它没有使用任何的非静态局部变量或形参,所以它没能捕获任何东西。更糟糕的是lambda表达式的代码中使用了静态变量divisor,每次调用addDivisorFilter后,divisor会递增,使得添加到filters中的每个lambda表达式的行为都不一样。如果一开始就避免使用按值的默认捕获模式,也就能消除代码被误读的风险了。

32:使用初始化捕获将对象移入闭包

有时按值捕获和按引用捕获并不能满足所有的需求。比如想要把move-only对象(如std::unique_ptr或std::future)放入闭包,或者想把复制昂贵而移动低廉的对象移入闭包时,C++11没有提供可行的方法,但是C++14为对象移动提供了直接支持。

实际上,C++14提供了一种全新的捕获方式,按移动的捕获只不过是该机制能够实现的多种效果之一罢了。这种方式称为初始化捕获(init capture),它可以做到C++11的捕获形式所有能够做到的事情(除了默认捕获模式,而这是需要远离的),不过初始化捕获的语法稍显啰嗦,如果C++11的捕获能解决问题,则大可以使用之。

下面是初始化捕获实现移入捕获的例子:

class Widget {
public:bool isValidated() const;bool isProcessed() const;bool isArchived() const;
private:…
};
auto pw = std::make_unique<Widget>();
…
auto func = [pw = std::move(pw)] { return pw->isValidated() && pw->isArchived(); };

上面的例子中,位于”=”左侧的pw,是lambda创建的闭包类中成员变量的名字;而位于”=”右侧的是其初始化表达式,所以”pw=std::move(pw)”表达了在闭包类中创建一个成员变量pw,然后使用针对局部变量pw实施std::move的结果来初始化该成员变量。在lambda内部使用pw也是指的闭包类的成员变量。一旦定义lambda表达式之后,因为局部变量pw已经被move了,所以其不再掌握任何资源。

上面的例子还可以不使用局部变量pw:

auto func = [pw = std::make_unique<Widget>()]{ return pw->isValidated() && pw->isArchived(); };

这种捕获方式在C++14中还称为广义lambda捕获(generalized lambda capture)。

但是如果编译器尚不支持C++14,则该如何实现按移动捕获呢?要知道一个lambda表达式不过是生成一个类并创建一个该类的对象的手法罢了,并不存在lambda能做而手工不能做的事情,上面C++14的例子,如果使用C++11,可以写为:

class IsValAndArch {
public: using DataType = std::unique_ptr<Widget>;explicit IsValAndArch(DataType&& ptr) : pw(std::move(ptr)) {}bool operator()() const{ return pw->isValidated() && pw->isArchived(); }
private:DataType pw;
};
auto func = IsValAndArch(std::make_unique<Widget>());

这种写法要比使用lambda麻烦很多。

如果非要使用lambda实现按移动捕获,也不是全无办法,可以借助std::bind实现:把要捕获的对象移动到std::bind产生的函数对象中;给lambda表达式一个指向欲捕获的对象的引用。比如C++14中的写法:

std::vector<double> data;
… // populate data
auto func = [data = std::move(data)]{ /* uses of data */ };

如果采用C++11中使用std::bind和lambda的写法,等价代码如下:

std::vector<double> data;
… // as above
auto func = std::bind([](const std::vector<double>& data) { /* uses of data */ },std::move(data)
);

std::bind也生成函数对象,可以将它生成的对象称为绑定对象。std::bind的第一个实参是个可调用对象,接下来的所有实参表示传递给该对象的值。

绑定对象内含有传递给std::bind所有实参的副本。对于左值实参,绑定对象内对应的副本实施的是复制构造;对于右值实参,实施的是移动构造。上面的例子中,第二个实参是个右值,所以在绑定对象内,使用局部变量data移动构造其副本,这种移动构造动作正是实现模拟移动捕获的关键所在,因为把右值移入绑定对象,正是绕过C++11无法将右值移动到闭包的手法。

当一个绑定对象被调用时,它所存储的实参会传递给std::bind的那个可调用对象,也就是func被调用时,func内经由移动构造得到的data副本就会作为实参传递给那个原先传递给std::bind的lambda表达式。这个C++11写法比C++14多了一个形参data,该形参是个指向绑定对象内部的data副本的左值引用,这么一来,在lambda内对data形参所做的操作,都会实施在绑定对象内移动构造而得的data副本之上,与原局部变量data无关。

默认情况下,lambda闭包类中的operator()成员函数会带有const饰词,因此闭包里的所有成员变量在lambda表达式的函数体内都带有const饰词,但绑定对象内移动构造而得的data副本并不带有const饰词,所以为了防止该data部分在lambda表达式内被意外修改,lambda的形参就声明为常量引用。但是如果lambda表达式带有mutable饰词,则闭包中的operator()函数就不会在声明时带有const饰词,相应的做法就是在lambda声明中略去const:

auto func = std::bind([](std::vector<double>& data) mutable { /* uses of data */ },std::move(data)
);

绑定对象存储着传递给std::bind所有实参的副本,因此本例中的绑定对象就包含一份由第一个实参lambda表达式产生的闭包的副本。这么一来,该闭包的生命期就和绑定对象是相同的。

另外一个例子,下面是C++14的代码:

auto func = [pw = std::make_unique<Widget>()] { return pw->isValidated() && pw->isArchived(); }; 

如果使用C++11采用bind的写法:

auto func = std::bind([](const std::unique_ptr<Widget>& pw){ return pw->isValidated() && pw->isArchived(); },std::make_unique<Widget>()
);

33:要对auto&&类型的形参使用std::forward,则需要使用decltype

泛型lambda表达式(generic lambda)是C++14最振奋人心的特性之一:lambda表达式的形参列表中可以使用auto,它的实现直截了当,闭包类中的operator()采用模板实现。比如下面的lambda表达式,以及其对应的实现:

auto f = [](auto x){ return func(normalize(x)); };class SomeCompilerGeneratedClassName {
public:template<typename T> auto operator()(T x) const{ return func(normalize(x)); }…
}; 

这个例子中,lambda表达式对x的动作就是将其转发给normalize,如果normalize区别对待左值和右值,则该lambda表达式的实现是有问题的,正确的写法应该是使用万能引用并将其完美转发给normalize:

auto f = [](auto&& x)
{ return func(normalize(std::forward<???>(x))); };

这里的问题是,std::forward的模板实参”???”应该怎么写?

这里可以使用decltype(x),但是decltype(x)产生的结果,却与std::forward的使用惯例有所不同。如果传入的是个左值,则x的类型是左值引用,decltype(x)得到的也是左值引用;如果传入的是右值,则x的类型是右值引用,decltype(x)得到的也是右值引用,但是,std::forward的使用惯例是std::forward<T>,其中T要么是个左值引用,要么是个非引用。

再看一下条款28中std::forward的简单实现:

template<typename T>
T&& forward(remove_reference_t<T>& param) {return static_cast<T&&>(param);
}

如果客户代码想要完美转发Widget类型的右值,则按照惯例它应该采用Wdiget类型,而非引用类型来实例化std::forward,然后std::forard模板实例化结果是:

Widget&& forward(Widget& param) { return static_cast<Widget&&>(param);
}

如果使用右值引用实例化T,也就是Widget&&实例化T,得到的结果是:

Widget&& && forward(Widget& param) {return static_cast<Widget&& &&>(param);
}

实施了引用折叠之后:

Widget&& forward(Widget& param) {return static_cast<Widget&&>(param);
}

经过对比,发现这个版本和T为Widget时的std::forward是完全一样的,因此,实例化std::forward时,使用一个右值引用和使用非引用类型,结果是相同的。所以,我们的完美转发lambda表达式如下:

auto f =[](auto&& param){return func(normalize(std::forward<decltype(param)>(param)));};

稍加改动,就可以得到能接收多个形参的完美转发lambda式版本,因为C++14中的lambda能够接受变长形参:

auto f =[](auto&&... params){return func(normalize(std::forward<decltype(params)>(params)...));};

34:优先使用lambda表达式,而非std::bind

std::bind在2005年就已经是标准库的组成部分了(std::tr1::bind),这意味着std::bind已经存在了十多年了,你可能不太愿意放弃这么一个运作良好的工具,然而有时候改变也是有益的,因为在C++11中,相对于std::bind,lambda几乎总会是更好的选择,而到了C++14,lambda简直已成了不二之选。

lambda表达式相对于std::bind的优势,最主要的是其具备更高的可读性:

// typedef for a point in time (see Item 9 for syntax)
using Time = std::chrono::steady_clock::time_point;
// see Item 10 for "enum class"
enum class Sound { Beep, Siren, Whistle };
// typedef for a length of time
using Duration = std::chrono::steady_clock::duration;
// at time t, make sound s for duration d
void setAlarm(Time t, Sound s, Duration d);// setSoundL ("L" for "lambda") is a function object allowing a
// sound to be specified for a 30-sec alarm to go off an hour
// after it's set
auto setSoundL = [](Sound s){// make std::chrono components available w/o qualificationusing namespace std::chrono;setAlarm(steady_clock::now() + hours(1), s, seconds(30));};

这里的lambda表达式,即使是没什么经验的读者也能看出来,传递给lambda的形参会作为实参传递给setAlarm。到了C++14中,C++14提供了秒,毫秒和小时的标准字面值,所以,可以写成这样:

auto setSoundL =
[](Sound s)
{using namespace std::chrono;using namespace std::literals;setAlarm(steady_clock::now() + 1h, s, 30s);
};

而下面的代码是使用std::bind的等价版本,不过实际上它还有一处错误的,后续在解决这个错误:

using namespace std::chrono;
using namespace std::literals;
using namespace std::placeholders; // needed for use of "_1"
auto setSoundB = // "B" for "bind"std::bind(setAlarm, steady_clock::now() + 1h, _1, 30s);

对于初学者而言,占位符”_1”简直好比天书,而即使是行家也需要脑补出从占位符数字到它在std::bind形参列表中的位置映射关系,才能理解在调用setSoundB时传入的第一个实参,会作为第二个实参传递给setAlarm。该实参的类型在std::bind的调用过程中是未加识别的,所以还需要查看setAlarm的声明才能决定应该传递何种类型的实参到setSoundB。

这段代码的错误之处在于,在lambda表达式中,表达式”steady_clock::now() + 1h”是setAlarm的实参之一,这一点清清楚楚,该表达式会在setAlarm被调用时求值,这样是符合需求的,就是需要在setAlarm被调用的时刻之后的一个小时启动报警。但是在std::bind中,”steady_clock::now() + 1h”作为实参传递给std::bind,而非setAlarm,该表达式在调用std::bind时就进行求值了,并且求得的结果会存储在绑定对象中,这导致的结果是报警的启动时刻是在std::bind调用之后的一个小时,而非setAlarm调用之后的一个小时。

要解决这个问题,就需要std::bind延迟表达式的求值到调用setAlarm的时刻,实现这一点,就是需要嵌套第二层std::bind的调用:

auto setSoundB =std::bind(setAlarm,std::bind(std::plus<>(), steady_clock::now(), 1h),_1,30s);

在C++14中,标准运算符模板的模板类型实参大多数情况下可以省略不写,所以此处也没必要在std::plus中提供了,而C++11中还没有这样的特性,所以在C++11中,想要实现上面的代码,只能是:

using namespace std::chrono; // as above
using namespace std::placeholders;
auto setSoundB =std::bind(setAlarm,std::bind(std::plus<steady_clock::time_point>(), steady_clock::now(), hours(1)),_1,seconds(30));

如果对setAlarm实施了重载,则又会有新的问题:

enum class Volume { Normal, Loud, LoudPlusPlus };
void setAlarm(Time t, Sound s, Duration d, Volume v);
auto setSoundL =
[](Sound s)
{using namespace std::chrono;setAlarm(steady_clock::now() + 1h, s, 30s);
};

即使有了重载,lambda表达式依然能正常工作,重载决议会选择有三个参数版本的setAlarm。但是到了std::bind,就没办法通过编译了:

auto setSoundB = std::bind(setAlarm, std::bind(std::plus<>(),steady_clock::now(),1h),_1,30s);

这是因为编译器无法确定应该将哪个setalarm传递给set::bind,它拿到的所有信息只有一个函数名。为了使std::bind能够通过编译,setAlarm必须强制转换到适当的函数指针类型:

using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundB = std::bind(static_cast<SetAlarm3ParamType>(setAlarm),std::bind(std::plus<>(), steady_clock::now(), 1h),_1,30s);

但是这么做又带来了lambda和std::bind的另一个不同之处。在lambda生成的setSoundL的函数调用运算符中,调用setAlarm采用的是常规函数唤起方式,这么一来,编译器就可以用惯常的手法将其内联:

setSoundL(Sound::Siren); // body of setAlarm may well be inlined here

而std::bind调用中使用了函数指针,这意味着在setSoundB的函数调用运算符中,setAlarm是通过函数指针来调用的,编译器一般无法将函数指针发起的函数调用进行内联,所以lambda表达式就有可能生成比std::bind更快的代码。

在setAlarm例子中,仅仅涉及了函数的调用而已,如果你想做的事比这更复杂,则lambda表达式的优势则更加明显。比如:

auto betweenL =[lowVal, highVal](const auto& val){ return lowVal <= val && val <= highVal; };

这里的lambda使用了捕获。std::bind要想要实现同样的功能,必须用比较晦涩的方式来构造代码,下面分别是C++14和C++11的写法:

using namespace std::placeholders;
auto betweenB =std::bind(std::logical_and<>(),std::bind(std::less_equal<>(), lowVal, _1),std::bind(std::less_equal<>(), _1, highVal));auto betweenB = std::bind(std::logical_and<bool>(),std::bind(std::less_equal<int>(), lowVal, _1),std::bind(std::less_equal<int>(), _1, highVal));

还是需要使用std::bind的延迟计算方法。

再看下面的代码:

enum class CompLevel { Low, Normal, High };
Widget compress(const Widget& w, CompLevel lev); //make compressedcopy of w
Widget w;
using namespace std::placeholders;
auto compressRateB = std::bind(compress, w, _1);

这里的w传递给std::bind时,是按值存储在std::bind生成的对象中的,在std::bind的调用中,按值还是按引用存储只能是牢记规则。std::bind总是复制其实参,但是调用方可以通过对实参实施std::ref的方法达到按引用存储的效果,因此:

auto compressRateB = std::bind(compress, std::ref(w), _1);

结果就是compressRateB的行为如同持有的是个指向w的引用,而非其副本。

而在lambda中,w无论是按值还是按引用捕获,代码中的书写方式都很明显:

auto compressRateL =
[w](CompLevel lev)
{ return compress(w, lev); };

同样明显的还有形参的传递方式:

compressRateL(CompLevel::High); // arg is passed by value
compressRateB(CompLevel::High); // how is arg passed?

Lambda返回的闭包中,很明显实参是按值传递给lev的;而在std::bind返回绑定对象中,形参的传递方式是什么呢?这里也只能牢记规则,绑定对象的所有实参都是按引用传递的,因为此种对象的函数调用运算符使用了完美转发。

总而言之,lambda表达式要比std::bind可读性更好,表达能力更强,运行效率也可能更好,在C++14中,几乎没有std::bind的适当用例,而在C++11中,std::bind仅在两个受限场合还算有使用的理由:

移动捕获,C++11没有提供移动捕获的语法,参考上一条款;

多态函数对象,因为绑定对象的函数调用运算符使用了完美转发,所以可以接收任何类型的实参,因此当需要绑定的对象具有一个函数调用运算符模板时,是有利用价值的:

class PolyWidget {
public:template<typename T>void operator()(const T& param);…
};PolyWidget pw;
auto boundPW = std::bind(pw, _1);
boundPW(1930); // pass int to PolyWidget::operator()
boundPW(nullptr); // pass nullptr to PolyWidget::operator()
boundPW("Rosebud"); // pass string literal to PolyWidget::operator()

C++11中的lambda表达式没有办法实现这一点,但是在C++14中,使用带有auto类型形参的lambda表达式可以很容易的实现这一点:

auto boundPW =
[pw](const auto& param)
{ pw(param); };

转载于:https://www.cnblogs.com/gqtcgq/p/9937013.html

Effective Modern C++:06lambda表达式相关推荐

  1. 《Effective Modern C++》笔记

    文章目录 绪论 第1章 型别推导 条款1:理解模板类型推导 情况1:ParamType 是个指针或引用,但不是万能引用 情况2:ParamType是万能引用 情况3:ParamType既非指针也非引用 ...

  2. Effective Modern C++ 纯人工翻译,持续更新,不为博你眼球,旨在自我提升

    文章目录 Effective.Modern.C++ 关键词翻译 Argument Parameter ParamType expr .expression type deduction trailin ...

  3. 【More Effective C#】Lambda表达式优化

    [More Effective C#]Lambda表达式优化 2010-10-19 08:09 by 空逸云, 2923 阅读, 38 评论, 收藏, 编辑 使用Lambda表达式将会造成Lambda ...

  4. [读书笔记]《Effective Modern C++》—— 移步现代 C++

    文章目录 前言 item7:区别使用 () 和 {} 创建对象 item8:优先考虑使用 nullptr 而不是 0 或者 NULL item9:优先考虑别名声明而非 typedefs item10: ...

  5. 《Effective Modern C++》读书笔记

    Tags: c++ Note:为避免各种侵权问题,本文并没有复制原书任意文字(代码除外,作者已经声明代码可以被使用).需要原书完整中文翻译的读者请等待官方译本的发布. 正文 为了让本文更加清晰,依然还 ...

  6. 【More Effective C#】LINQ表达式与方法调用的映射

    LINQ构建在两个概念之上,一种查询语言和一系列将查询语言转换成方法调用的实现.在编译时,编译器将LINQ表达式(LINQ to object)转换成方法调用. .Net基础类库提供了两种扩展方法.S ...

  7. Effective Modern C++翻译(3)-条款2:明白auto类型推导

    条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一 ...

  8. Effective Modern C++ 第四章,C++智能指针

    智能指针 Smart Pointer 一些说明: C++98中有智能指针std::auto_ptr,在C++11中,出现std::unique_ptr.std::unique_ptr包含了std::a ...

  9. Effective Modern C++ 第三章第二节,C++新特性

    Chapter 3-2: Moving to Modern C++ Item 11: Prefer deleted functions to private undefined ones C++中,有 ...

最新文章

  1. 为什么Redis内存不宜过大
  2. pil numpy转换二值图
  3. 2022年全球及中国真空用光纤馈通件行业运营形式与未来投资动向调研报告
  4. linux 监控命令
  5. ubuntu 编译 /usr/bin/ld: cannot find 问题解决
  6. 2020年中国地摊经济行业报告
  7. 职称计算机excel2015年,【2015年职称计算机Excel重点精讲:创建数据透视表】- 环球网校...
  8. Python网路请求(GET示例)
  9. 从零开始编写自己的C#框架(11)——创建解决方案
  10. iptraf使用心得——如何查看网络流量
  11. Python代码缩进
  12. 2021-10-19 资源收藏
  13. 前端学习——页面布局
  14. 从win10体验到重装win8
  15. word文档字不靠边_word怎么调整单元格文字边距表格文字紧靠边框怎么办
  16. 即将前往下一个饭局,你的牙还好吗?丨钛空舱爆款春节特献
  17. html实现正方体原理,CSS3 实现正方体
  18. ToC产品和ToB产品的区别
  19. 【元宇宙欧米说】从GameFi的视角讨论Web2到Web3的利弊
  20. 爆火交友一元脱单、盲盒、微信公众号制作【源码】

热门文章

  1. 关于vue.js element ui 表单验证 this.$refs[formName].validate()的问题
  2. shopt_Linux命令
  3. js 字符串操作函数有哪些
  4. ssm项目之maven添加pom jar包配置
  5. Ajax框架,DWR介绍,应用,样例
  6. android call require api level
  7. 框架设计:实现数据的按需更新与插入的改进--用数据对比进一步说明
  8. 深入探索.NET框架内部了解CLR如何创建运行时对象
  9. c++如何对结构体作为形参设置默认值
  10. Dynamics AX 2009 启动时提示“…the application files in exclusive mode”的解决