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 > ; }; // 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 funcs void addDivisorFilter()
{
auto calc1 = computeSomeValue1();
auto calc2 = computeSomeValue2();
auto divisor = computeDivisor(calc1, calc2);
filters.emplace_back(
[&](int value) { return value % divisor == ; }
);
}

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

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

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

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

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

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

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

对于这个例子而言,这样做确实是没问题的。但是按值的默认捕获并非一定能避免空悬引用,问题在于如果按值捕获了一个指针,在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 == ; }
);
}

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

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

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

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

但是为什么一开始的代码没有发生编译错误呢?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 == ; }
);
}

因此,该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 == ; }
);
}

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

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

对广义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 == ; }
);
++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 qualification
using namespace std::chrono;
setAlarm(steady_clock::now() + hours(), s, seconds());
};

这里的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,
seconds());

如果对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(); // 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); };

Effective Modern C++:06lambda表达式的更多相关文章

  1. Effective Modern C++翻译(1):序言

    /*********************************************************** 关于书: 书是我从网上找到的effective Modern C++的样章,内 ...

  2. 《Effective Modern C++》翻译--简单介绍

    北京时间2016年1月9日10:31:06.正式開始翻译.水平有限,各位看官若有觉得不妥之处,请批评指正. 之前已经有人翻译了前几个条目,有些借鉴出处:http://www.cnblogs.com/m ...

  3. [C++11] Effective Modern C++ 读书笔记

    本文记录了我读Effective Modern C++时自己的一些理解和心得. item1:模板类型推导 1)reference属性不能通过传值参数传入模板函数.这就意味着如果模板函数需要一个refe ...

  4. 决定干点事儿--翻译一下《effective modern c++》

    写了非常多关于C++11的博客.总是认为不踏实,非常多东西都是东拼西凑.市场上也非常少有C++11的优秀书籍,但幸运的是Meyers老爷子并没有闲赋.为我们带来了<effective moder ...

  5. Effective Modern C++:04智能指针

    裸指针有着诸多缺点:裸指针的声明中看不出它指向的是单个对象还是数组:裸指针的声明中也无法看出使用完它指向的对象后是否需要删除,也就是声明中看不出裸指针是否拥有其指向的对象:即使知道要析构裸指针指向的对 ...

  6. Effective Modern C++:03转向现代C++

    07:在创建对象时注意区分()和{} 自C++11以来,指定初始化值的的方式包括使用小括号,等号,以及大括号: ); // initializer is in parentheses ; // ini ...

  7. Effective Modern C++:01类型推导

    C++的官方钦定版本,都是以ISO标准被接受的年份命名,分别是C++98,C++03,C++11,C++14,C++17,C++20等.C++11及其后续版本统称为Modern C++. C++11之 ...

  8. 转:【More Effective C#】Lambda表达式优化

    http://www.cnblogs.com/kongyiyun/archive/2010/10/19/1855274.html 使用Lambda表达式将会造成Lambda表达式主题部分的代码重复. ...

  9. Effective Modern C++翻译(7)-条款6:当auto推导出意外的类型时,使用显式的类型初始化语义

    条款6:当auto推导出意外的类型时,使用显式的类型初始化语义 条款5解释了使用auto来声明变量比使用精确的类型声明多了了很多的技术优势,但有的时候,当你想要zag的时候,auto可能会推导出了zi ...

随机推荐

  1. windows下docker 启动jenkins成功,浏览器无法访问,拒绝了我们的连接

    [问题现象] 在Windows下使用docker启动了一个jenkins,翻越了无数的坑,最后的启动命令为 docker run --name jenkins -u root -p 8000:8000 ...

  2. idea长期使用

    0. 如果你的idea(版本2019.02)是已过期状态则先上网找个激活码激活再进行下面步骤延长使用期至2089年 1. 附件下载地址: 链接: https://pan.baidu.com/s/1Tp ...

  3. [转]C#截获本机数据包方法实例

    本文向大家介绍Windows Sockets的一些关于用C#实现的原始套接字(Raw Socket)的编程,以及在此基础上实现的网络封包监视技术.同Winsock1相比,Winsock2最明显的就是支 ...

  4. 在scrapy中利用Selector来提取数据

    1.创建对象 Selector类的实现位于scrapy.selector模块,创建Selector对象的时候,可以将页面的Html文档字符串传递给Selector构造器方法 2.选中数据 调用Sele ...

  5. KOA 学习(六)superAgent

    原文地址 http://www.2cto.com/kf/201611/569080.html 基本请求 初始化一个请求可以通过调用request模块中适当的方法,然后使用.end()来发送请求,例如一 ...

  6. jdk 数组位移运算

    1.采用先shift=31-Integer.numberOfLeadingZeros(scale);取int前面的补零个数31再减去拿到占得内存位长度 2.i偏移shift(其实等于I*位数) 加上b ...

  7. hive-hbase性能问题

    华为负责人本来想用这种表来做大数据开发,先前就听前辈讲过性能存在问题.实际开发过程确实存在不少问题.然后放弃换方案去做了. 1.底层meta映射字段问题.默认4000,如果再做修改会涉及到挺多源码. ...

  8. [Array]122. Best Time to Buy and Sell Stock II(obscure)

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  9. 【html、CSS、javascript-7】Dom

    文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式.我们最为关心的是,DOM把 ...

  10. JS字符串的相关方法

    1.字符方法 charAt()和charCodeAt(),都接收一个参数,charAt()返回的是给定位置的字符,charCodeAt()返回的是给定位置字符的字符编码.如: <script&g ...