C++ 新功能只是一个小目标(楚才国科)

你是否希望,在生产代码中,拥有更高版本的C ++?今天很多C ++开发人员,用的编译器,都不支持最新版本的标准。

其中可能有很多原因,也许你或你的客户,有很多遗留代码需要移植,也许你的硬件,没有足够的基础设施。

关键在于,语言提供的最新功能,并不能给大家带来好处,而且很遗憾的是,其中一些功能,肯定会让代码更具表现力。
这里写图片描述
但是,即使你无法使用这些功能,也不一定要放弃它们的好处。至少不用放弃全部。 有一些方法可以使用代码中新功能的思路,更准确地传达你的意图。

当然,这些方法肯定不如使用新版本C++本身的功能那么好,这就是你还是需要更新编译器的原因。 但与此同时,我将介绍7种方法来模拟这些功能,以最低的成本改进你的代码。

= default, = delete

在C++ 11中,= default可以向编译器发出指令生成以下内容之一:

• 一个默认的构造函数;

• 一个拷贝构造函数;

• 一个拷贝赋值运算符;

• 一个移动构造函数;

• 一个移动赋值运算符;

• 一个析构函数。

在某些情况下,编译器无论如何都会生成这些函数。但是对于C++ 11,一些开发人员喜欢在他们的界面中表现这一点,以向读者保证他们知道这些方法是自动生成的,并且这也是他们想要的类。

在C++ 11之前没有办法用原生的方法表现这一点。但你照样可以在注释中注明:

class X
{
/* X(const X& other) = default; */
/* X& operator=(const X& other) = default; */
/* ~X() = default;*/

// rest of X ...

};
类似地,为了阻止编译器生成这些函数,在C++ 11之前我们不得不将它们声明为private,并且不实现它们:

class X
{
// rest of X …
private:
X(const X& other);
X& operator=(const X& other);
};
在C++ 11中,我们可以将它们声明为public,并通过“= delete”禁止编译器生成这些函数。

在C++ 11之前,我们需要更加明确,不仅需要声明为private,还需要设置“= delete”(但不是真的设置,只是加注释):

class X
{
// rest of X …
private:
X(const X& other) /* = delete */;
X& operator=(const X& other) /* = delete */;
};

标准算法

实用的STL算法库随着新版本C++的出现而不断发展,不断加入新算法。其中一些算法非常泛用。例如copy_if,或all_of,以及其类似的any_of和none_of。

听起来令人惊讶,但在C++ 11之前它们并不是标准算法。

但是在C++ 11之前的代码库中访问它们的方法非常简单:只需去某个参考网站(例如cppreference.com)上复制它们的实现方法(copy_if的实现:https://en.cppreference.com/w/cpp/algorithm/copy;all_of及其类似的算法的实现:https://en.cppreference.com/w/cpp/algorithm/all_any_none_of),然后把实现方法粘贴到你的代码就可以了。整个操作大约需要10秒钟,通过在代码中使用它们可以节省更多时间。

属性

属性是方括号之间的关键字:[[example_attribute]]。它们是C++ 11中引入的,在C++ 17中更多属性被加了进来。对于属性的深入分析,你可以参照Bartek的文章《详解C++ 17:属性》(https://www.bfilipek.com/2017/07/cpp17-in-details-attributes.html),但属性的一般概念是你可以将它们当作代码中的标记,用以向阅读代码的人和编译器表达你的意图。

我们以[[fallthrough]]属性为例。这个属性可以在switch语句中使用,假设你故意没有在其中一个case中加break,那么为了执行如下case代码:

switch (myValue)
{
case value1:
{
// do something
break;
}
case value2:
{
// do something
}
case value3:
{
// do something
break;
}
}
注意这里的case Value2没有break。这不免让人担心会出bug。大多数情况下它就是个bug,除非你想同时执行case Value2和其他case语句。如下所示,[[fallthrough]]可以更明确地表达这一点:

switch (myValue)
{
case value1:
{
// do something
break;
}
case value2:
{
// do something
[[fallthrough]];
}
case value3:
{
// do something
break;
}
}
它可以防止编译器报错,也可以向其他开发人员表明:你在写这段代码的时候,知道自己在干什么。

在C++ 17之前,如果你想利用这个技巧来省略break的话,那么尽管依然会收到警告,但是至少你可以通过[[fallthrough]]向其他开发者表明你的意图:

switch (myValue)
{
case value1:
{
// do something
break;
}
case value2:
{
// do something
//[[fallthrough]];
}
case value3:
{
// do something
break;
}
}
C++ 11和C++ 17的其他属性也有类似的功能。

概念

概念是C++非常令人期待的特性,它通常应该属于C++ 20的一部分。概念本质上是模板的接口。概念允许编写比typename更精确的东西来定义模板参数。实际上,typename仅表示“这是一种类型”,却并没有说明该类型的任何其他内容。

像Iterator这样的概念应该替换模板代码中操作迭代器的typename,而且Iterator应该被定义为拥有特定的操作(递增,解引用等)。传递没有这些特定操作的类型将会造成编译错误,并产生明确的错误消息,以解释为什么该类型不是预期的Iterator。

我不打算想你介绍如何在C++语言引入这些之前,自行模拟概念。这是一个非常棘手的事情,如果你想了解实现方法,那么可以看看range-v3(https://github.com/ericniebler/range-v3),它使用非常先进的技术来模拟这个功能。

我建议你用更容易方法:谨慎选择模板参数名称,并尽可能使用概念的名称。即使你无法在拥有概念之前替换typename,但是你依然有很大的自由来选择类型参数的名称。

以在为Iterator示例时,不要把将模板参数命名为typename命名为T或typename I,而是命名为使用typename Iterator。我们永远不会因为某个变量是int而叫它int i,但对于模板类型,面对模板类型时我们会更倾向于这么做。

模板类型的名称在模板代码中到处都是,所以让我们给它取一个好名字,并使用正在开发的概念的标准名称。当C++(以及我们的代码库)实际引入概念时,良好的命名可以让我们的代码非常妥帖。

范围算法

STL是一个很棒的库,但有个东西用起来有点麻烦:迭代器。实际上,每个STL都接受两个迭代器,以定义算法需要操作的输入范围。

当你需要将算法应用在范围的一部分上时,这个功能很有用,但如果要遍历整个范围(绝大多数情况下如此),迭代器就很碍事了:

auto positionOf42 = std::find(begin(myCollection), end(myCollection), 42);
如果能将范围作为整体传递就会方便许多:

auto positionOf42 = std::find(myCollection, 42);
这就是有关范围的提案在C++ 20里的目标(同时还有许多其他功能)。但这个功能即使在C++ 98中也很容易模拟,只需要将调用STL算法的语句包裹在一个接受范围的函数中即可:

template

猜你喜欢

转载自blog.csdn.net/weixin_43092451/article/details/82625345