- 本篇笔记汇总了C++14中的主要新语言功能特性,根据个人理解与查阅的资料进行记录。
- 主要参考地址:cppreference
- C++14主要是在C++11标准之上的一些补充,所以相对的内容较少一些。
· 变量模板
??在之前的版本中,模板均为函数模板或者类模板,而从C++14起,变量也可以使用模板了。变量模板的语法为 template < 形参列表 > 变量声明 ,通常模板的规则都适用于变量模板,比如说特化什么的。下面是几个例子:
template<class T>
constexpr T pi = T(3.1415926535897932385L);
template<class T>
T circular_area(T r)
{
return pi<T> * r * r;
}
int main()
{
double a = 3;
cout << circular_area(a) << endl;
cout << circular_area<double>(2) << endl;
return 0;
}
输出:
28.2743
12.5664
??与其他静态成员一样,静态数据成员模板的需要一个定义。这种定义可以在类定义外提供。处于命名空间作用域的静态数据成员的模板声明也可以是类模板的非模板数据成员的定义:
struct limits
{
template<typename T>
static const T min;
};
template<typename T>
const T limits::min = { };
template<class T>
class X
{
static T s;
};
template<class T>
T X<T>::s = 0;
??在C++14引入变量模板前,参数化变量通常实现为类模板的静态数据成员,或返回所需值的 constexpr 函数模板,这点在STL源码里可以经常看见。但是说实话可能变量模板这个新特性比较新或者说非他不可的场景比较少,暂时我还没有用到过或者说在源码里见到过这个东西。
?
· 泛型 lambda
??在C++11中,lambda 表达式的形参需要被声明为具体的类型。而在C++14中其放宽了这一要求,这就使得 lambda 表达式的形参声明中可以使用类型说明符 auto ,从而达成泛型lambda的目的。
int main()
{
auto lambda = [](auto x, auto y)
{
return x + y;
};
cout << lambda(1.1, 2) << endl;
cout << lambda(1, 2) << endl;
cout << lambda('a', 'b') << endl;
return 0;
}
输出:
3.1
3
195
??泛型 lambda 表达式遵循模板参数推导的规则。上面这段代码中的泛型 lambda 表达式的作用与下面的代码相同:
struct unnamed_lambda
{
template<typename T, typename U>
auto operator()(T x, U y) const
{
return x + y;
}
};
auto lambda = unnamed_lambda();
??这东西我感觉使用起来还是挺方便的,毕竟某些场景泛型 lambda 会节省很多功夫。不过就我而言需求也没那么强,因为我个人用到 lambda 的场景一般对于 lambda 的传参都已经有明确的定义了,这时就没什么泛型编程的需求。
?
· lambda 初始化捕获
??C++11的 lambda 表达式通过值拷贝和引用来捕获在外层作用域声明的变量。这就意味着 lambda 的值成员不可以是 move-only 类型,比如说智能指针 std::unique_ptr ,它就是个仅移动的类型。而在C++14中,lambda 表达式放开限制,允许对被捕获的成员用任意的表达式初始化。
??这样放开限制的第一个好处就是可以通过 std::move 来初始化捕获一个 move-only 类型的变量,如下例:
int main()
{
unique_ptr<int>p1 = make_unique<int>(10);
cout << "1. main() p1:" << *p1 << endl;
auto lambda = [p1 = move(p1)]()
{
cout << "2. lambda() p1:" << *p1 << endl;
};
lambda();
if(!p1)
{
cout << "3. main() p1 is empty" << endl;
}
return 0;
}
输出:
1. main() p1:10
2. lambda() p1:10
3. main() p1 is empty
??而第二个好处就是可以任意声明 lambda 的值成员,而不需要外层作用域有一个具有相应名字的变量,如下例:
int main()
{
auto lambda = [a = 12345]()
{
cout << "lambda() a:" << a << endl;
};
lambda();
return 0;
}
输出:
lambda() a:12345
??这个新特性我觉得挺有用的,让 lambda 更加的灵活和方便,虽然我还没用过。但是有一点需要注意,声明 lambda 的时候会直接执行里面的捕获语句,所以被捕获的 move-only 类型变量会直接失效…比如下面这个,即使不执行 lambda 函数也会失效。
int main()
{
unique_ptr<int>p1 = make_unique<int>(10);
cout << "1. main() p1:" << *p1 << endl;
auto lambda = [p1 = move(p1)]()
{
┊ cout << "2. lambda() p1:" << *p1 << endl;
};
if(!p1)
{
┊ cout << "3. main() p1 is empty" << endl;
}
return 0;
}
输出:
1. main() p1:10
3. main() p1 is empty
?
· new/delete 消除
??贴一个stackoverflow上的相关回答(渣翻): 点我跳转
-
问: ??鉴于 make_unique 和 make_shared 的可用性,以及 unique_ptr 和 shared_ptr 的自动析构函数,在C++14中使用 new 和 delete 的情况是什么(除了支持遗留代码)? -
答: ??虽然在许多情况下智能指针比原始指针更可取,但 new/delete 在 C++14 中仍然有很多用例。 ??如果您需要编写任何需要就地构建的内容,例如: ???1.一个内存池 ???2.一个分配器 ???3.标记变体 ???4.到缓冲区的二进制消息 ??您将需要使用 new 和 delete 。 ??以及对于一些需要编写的容器,您可能希望使用原始指针进行存储。 ??即使对于标准的智能指针,如果您想使用自定义删除器,您仍然需要 new ,因为 make_unique 和 make_shared 不允许这样做。 ?
· constexpr 函数上放松的限制
??C++11引入了声明为 constexpr 的函数的概念。声明为 constexpr 函数的意义是:如果其参数均为合适的编译期常量,则对这个 constexpr 函数的调用就可用于期望常量表达式的场合(如模板的非类型参数,或枚举常量的值)。如果参数的值在运行期才能确定,或者虽然参数的值是编译期常量,但不符合这个函数的要求,则对这个函数调用的求值只能在运行期进行。然而C++11要求 constexpr 函数只含有一个将被返回的表达式(也可以还含有 static_assert 声明等其它语句,但允许的语句类型很少)。
??在C++14中将放松这些限制,但 goto 仍然不允许在 constexpr 函数中出现,其中可以含有以下内容:
- 任何声明,除了:
1.static 或 thread_local 变量。 2.没有初始化的变量声明。 - 条件分支语句
if 和 switch 。 - 所有的循环语句,包括基于范围的
for 循环。 - 表达式可以改变一个对象的值,只需该对象的生命期在声明为
constexpr 的函数内部开始。包括对有 constexpr 声明的任何非 const 非静态成员函数的调用。
??此外,C++11指出,所有被声明为 constexpr 的非静态成员函数也隐含声明为 const (即函数不能修改*this的值)。这点已经被删除,非静态成员函数可以为非 const 。
??怎么说呢,这东西在标准库里挺常见的,因为标准库要进行泛型编程,用模板和 constexpr 关键字的频率很高。但是在我个人的编程中,感觉用 constexpr 修饰的函数比较少吧…所以还没咋用过这种复杂点的 constexpr 函数。
?
· 二进制字面量
??C++14的数字可以使用二进制形式指定,其格式使用前缀 0b 或 0B ,类似之前的十六进制前缀 0x 。没啥好说的。
int main()
{
int x = 0x666;
int y = 0b101;
cout << x << endl << y << endl;
return 0;
}
输出:
1638
5
?
· 数位分隔符
??C++14引入单引号 ' 作为数字分位符号,使得数值型的字面量可以具有更好的可读性。
??Ada、D语言、Java、Perl、Ruby等程序设计语言使用下划线 _ 作为数字分位符号,C++之所以不和它们保持一致,是因为下划线已被用在用户自定义字面量的语法中。
int main()
{
auto integer_literal = 100'0000;
auto floating_point_literal = 1.797'693'134'862'315'7E+308;
auto binary_literal = 0b0100'1100'0110;
auto silly_example = 1'0'0'000'00;
cout << integer_literal << endl << floating_point_literal << endl;
cout << binary_literal << endl << silly_example << endl;
return 0;
}
输出:
1000000
1.79769e+308
1222
10000000
??这东西我觉得挺有用的,由上面的例子可以看出来这个 ' 是随意加的,编译器会忽略它,所以可以按自己的喜好或者约定来添加分隔符。
?
· 函数的返回类型推导
??C++11允许 lambda 表达式根据 return 语句的表达式类型推断返回类型。C++14为一般的函数也提供了这个能力,此外C++14还拓展了原有的规则,使得函数体并不是 {return expression;} 形式的函数也可以使用返回类型推导。
??为了启用返回类型推导,函数声明必须将 auto 作为返回类型,但没有C++11的后置返回类型说明符:
auto DeduceReturnType();
??如果函数实现中含有多个 return 语句,这些表达式必须可以推断为相同的类型。使用返回类型推导的函数可以前向声明,但在定义之前不可以使用。它们的定义在使用它们的翻译单元(translation unit)之中必须是可用的。这样的函数中可以存在递归,但递归调用必须在函数定义中的至少一个 return 语句之后:
auto Correct(int i)
{
if (i == 1)
return i;
else
return Correct(i-1)+i;
}
auto Wrong(int i)
{
if(i != 1)
return Wrong(i-1)+i;
else
return i;
}
??这是我个人认为此次更新最有用的一个内容了,auto 返回值可以节省很多功夫。不过坏处就是不太明了,类型比较复杂的时候可读性比较差,但是我觉得一般的小场景用用还是很舒服的。
?
· 带默认成员初始化器的聚合类。
??C++11新增 member initializer ,这是一个表达式,被应用到类作用域的成员上,如果构造函数没有初始化这个成员。聚合体的定义被改为明确排除任何含有 member initializer 的类,因此,他们不允许使用聚合初始化。
??C++14将放松这一限制,这种类型也允许聚合初始化。如果花括号初始化列表不提供该参数的值,member initializer 会初始化它。 ?
|