【现代C++】使代码简洁的两种解包方式

(点击上方公众号,可快速关注)

前言

C++没有模式匹配,在提取某个数据类型的元素或成员时会显得不太方便。假设t是一个包含3个元素的元组,类型为std::tuple,取每个元素的代码如下:

auto i = std::get<0>(t);
auto d = std::get<1>(t);
auto b = std::get<2>(t);

需要三行语句,每行语句的功能基本相同,除了索引不一样,显得冗余;相信很多同学的做法是:将第一行代码复制两份,在此基础上修改为后两句,这样出错就容易多了,万一忘了改索引呢。

上面的例子是关于元组的,std::pair<>是元组的特列,也能通过通用的std::get方式获取元素。除此之外,还提供了firstsecond成员变量,较之元组简单了不少,但名字太“抽象”,完全体现不出数据的含义,可读性不高。

我们看下支持模式匹配的Haskell是怎么做的:

let (i, d, b) = t

只用一行代码,是不是特简洁;而且可以给元素命名,可读性也很高。很幸运,现代C++提供了两种工具,能实现与之类似的效果(当然还远不如Haskell强大),下面将分别介绍。

std::tie

C++11通过标准库的方式提供,用于std::tuplestd::pair类型的数据解包。函数签名为:

#include 
template< class... Types >
tuple tie( Types&... args ) noexcept;

从参数和返回值类型上能大体猜到这个函数的工作原理:它实际上将绑定变量通过引用方式传入tie函数,tie函数将这些参数组装成一个tuple,等号右侧的tuple的整体赋值给这个tuple,从而实现tuple每个位置的值都绑定到相应的变量上。

使用方式也比较简单,先声明绑定变量,然后将这些变量传入tie函数:

int i;
double d;
bool b;
std::tie(i, d, b) = t;

代码已经有点Haskell的味道了,虽然解包的代码少了,但绑定的变量需要事先声明,显得代码不少。

在Haskell中,如果不想捕获某个值,可以使用wildcard。如,程序可能用不到第2个值,可以这么写:

let (i, _, b) = t

std::tie也提供了类似的功能,使用std::ignore代替变量即可:

int i;
bool b;
std::tie(i, std::ignore, b) = t;

特别说明下,tie函数也可以对std::pair解包,因为std::tuple重载了赋值操作符,所以赋值操作符右侧为pair的情况也能处理。

template< class U1, class U2 >
constexpr tuple& operator=( const pair& p );

template< class U1, class U2 >
constexpr tuple& operator=( pair&& p );

tie函数使用起来比较简单,但也没多大亮点。仅仅采用标准库实现的,限制还是比较多的:

  • 绑定变量必须先定义

    某些类型的变量可能不太容易声明,这样就很难使用tie获取元素。

  • 适用范围窄

    仅用于tuple和pair

  • 只有拷贝语义

    解包时,右侧的元素值只能复制给左侧的值,如果对象较大,复制成本会比较高。

Structured Bindings

C++17中引入的一个语言特性,叫做Structured Binding,有了它,就不必用std::tie了。这一部分主要介绍这个新的语言特性的用法,通过示例代码给出。

这个语法支持三种类型的结构的绑定,下面一一说明:

绑定数组的元素

C++主要有两种数组类型:

  • 继承自C语言的原始数组

  • C++11标准库引入的std::array类型

都支持结构绑定:

int rawArray[] = {1, 2};
std::array array {3, 4};

auto [x, y] = rawArray; // x=1, y=1
auto [a, b] = array;    // a=3, b=4

结构绑定配合auto关键字使用,方括号内的是元素被绑定的变量名,与数组的元素个数一致。上例中的绑定相当于将数组元素直接拷贝到对应的变量里,语义跟普通的赋值操作是一样的。普通的变量能支持constvolatile等修饰符,而且支持引用(&&&),结构绑定同样支持。

std::array array {std::string("a"), std::string("b")};

const auto& [a, b] = array;    // a="a", b="b"

上例中,ab是常量字符串的引用类型。注意const&auto三者之间的位置,可以根据实际需要省略const&,语义跟普通的变量是一致的,在后续的示例中就不突出这些用法了。

绑定tuple-like类型的元素

tuple-like类型是指行为跟tuple一致的类型,怎么才算行为一致,这里留个坑,后续填。现在只需知道标准库的std::tuplestd::pair肯定是tuple-like类型即可。

用法示例如下:

auto p = std::make_pair(1, 2);
auto t = std::make_tuple(3, 4);

auto [x, y] = p;  // x=1, y=1
auto [a, b] = t;  // a=3, b=4
绑定类的数据成员

还能绑定结构体数据成员,需要保证被绑定的数据成员是public的。

struct Person {
    std::string name;
    int age;
};

Person p {"name", 66};
auto [n, a] = p;  // n="name", a=66

是不是很简单。

小结

std::tieStructured Binding之间的选择问题:如果编译器支持C++17,当然推荐后者。在std::tie一节末尾列出的3个限制,Structured Binding都比较完美地解决了,而且代码简洁清晰。保持代码简洁、可读性好就是提高生产力。

喜欢我的文章,请关注我的公众号。转载请标明出处。

【现代C++】使代码简洁的两种解包方式_第1张图片

你可能感兴趣的:(编程语言,java,python,javascript,ios)