跟我学C++中级篇—constexpr在字符串上的应用
一、const和constexpr
先看几个小的例子来引出相关的问题:
const std::string s1 = \"hello\";const std::string_view s2 = \"world\";constexpr std::string s3 = \"come\";//编译错误,报\"s3 is not literal\"constexpr std::string_view s4 = \"back\";
上面的代码很容易在编译器中发现会有问题,大家会发现,string_view在const或constexpr修饰下都能正常编译,但string在const修饰下是没有问题,但如果使用constexpr修饰即类似上面的代码s3的情况下,会报一个字面量错误。什么是字面量?简单的说,字面量就是固定的值,常量。constexpr定义的s3不也是常量么?为什么不行呢?
二、分析
要想明白s3为什么不能直接赋值,就必须明白const和constexpr的本质不同。constexpr要求表达式必须在编译期是可确定和计算的。这就带来一个本质的问题,即如果表达式可能会出现内存的分配和释放,那么,基本就和constexpr无缘了。更不用提什么调用其它的非constexpr的表达式甚至抛出异常,想都不要想。但const为什么可以呢?因为它只是运行时的常量,而运行时就可以做内存分配和释放等动作了。
好,既然明白了const和constexpr的本质不同,再来看string_view和string就很好理解了。在标准库中,string_view的本质是一个包含两个成员的结构体,其中一个是指向字符串常量的指针,另外一个是其长度。换句话说,所谓的赋值操作对其来说是一个成本极其低廉的行为。但对string可就复杂多了,在前面的分析中提到过,string的处理,有多种情况,特别小的情况下还涉及到了SSO的优化。它的内存分配和管理都是一个相对复杂的成本较高的动作,而编译期一般是不允许内存的动态分配和释放的。另外string的成员函数也不是constexpr的,所以,如果一旦涉及到内部调用也是不允许的。
也可以这样理解,string_view是trivial,而string不是trivial。前者string_view只拥有一个字符串的视图不拥有真正的内存(即不进行深拷贝)也不进行内存的销毁,而string则需要支持深拷贝并且严格进行内存的管理释放。
用现代比较容易理解的话来说,string是拥有内在的所有权的,它的内存是动态变化的,而string_view是没有所有权的,其内存是固定的(注意不是指向字符串的内存而是其本身的内存,即指针和长度)。
三、 c++20后的扩展
但是,世界上唯一不变的就是变化。到c++20后,标准开始在受限的情况下对constexpr在string上的应用进行了放宽。基础的赋值操作被允许,即上面的s3那种情况编译是可以通过的。不过,在更广泛的应用上,仍然无法使用constexpr,比如类似于:
constexpr std::string s1 = \"hello \";constexpr std::string s2 = \" world!\";s1 = s2;//不允许
通过string和string_view的用法的变化,其实就可以明白,抽象的作用。
四、总结
一如现实世界,标准的大佬们的目的也是想在易用的前提下保障安全。就象现实世界中的防呆插头、专用扳手等等,常量和常量表达式,都起到了这样的作用。为了安全,可能会在后面不断的进行扩展相关的技术标准,从而让开发者在不知不觉中无缝的进行代码编写的安全升级。