为什么不允许 std::string 从字符数组初始化?

Why not allowing std::string initialization from array of chars?

在 C++ 中,您可以从 char *const char * 初始化一个 std::string 对象,这隐含地假设字符串将在第一个 NUL 字符处结束指针。

然而,在 C++ 中,字符串文字是数组,即使字符串文字包含嵌入的 NUL,也可以使用模板构造函数来获取正确的大小。例如,参见以下玩具实施:

#include <stdio.h>
#include <string.h>
#include <vector>
#include <string>

struct String {
    std::vector<char> data;
    int size() const { return data.size(); }

    template<typename T> String(const T s);

    // Hack: the array will also possibly contain an ending NUL
    // we don't want...
    template<int N> String(const char (&s)[N])
        : data(s, s+N-(N>0 && s[N-1]=='[=11=]')) {}

    // The non-const array removed as probably a lot of code
    // builds strings into char arrays and the convert them
    // implicitly to string objects.
    //template<int N> String(char (&s)[N]) : data(s, s+N) {}
};

// (one tricky part is that you cannot just declare a constructor
// accepting a `const char *` because that would win over the template
// constructor... here I made that constructor a template too but I'm
// no template programming guru and may be there are better ways).
template<> String::String(const char *s) : data(s, s+strlen(s)) {}

int main(int argc, const char *argv[]) {
    String s1 = "Hello[=11=]world\n";
    printf("Length s1 -> %i\n", s1.size());
    const char *s2 = "Hello[=11=]world\n";
    printf("Length s2 -> %i\n", String(s2).size());
    std::string s3 = "Hello[=11=]world\n";
    printf("std::string size = %i\n", int(s3.size()));
    return 0;
}

是否有任何特定的技术原因导致标准未考虑这种方法,而是嵌入 NULs 的字符串文字在用于初始化 std::string 对象时最终被截断?

使用包含嵌入空字节的文字初始化 std::string 需要将起始指针和长度都传递给构造函数。

如果有专用的 takes-array-reference 构造函数模板,那将是最简单的,但正如您所注意到的

  • 这样的模板,只有 数组参数,将被认为比仅采用 char const*

  • 不清楚是否应包含最终终止空值。

第一点意味着物理代码接口将是一个单一的模板化构造函数,其中只有文档(而不是您的编辑器的工具提示)会讲述它接受或不接受的完整故事。一种解决方法是引入一个额外的虚拟解析器参数。这降低了便利性。

第二点是引入bug的机会。构造函数最常见的用途无疑是普通的字符串文字。然后,它会不时地用于带有嵌入空字节的文字 and/or 数组,但奇怪的是最后一个字符被切掉了。

相反,可以简单地先命名值,

char const data[] = "*.com[=10=]*.exe[=10=]*.bat[=10=]*.cmd[=10=]";
string s( data, data + sizeof( data ) );    // Including 2 nulls at end.

综上所述,当我定义自己的字符串时 类 我已经包含了 takes-array-argument 构造函数,但出于与方便性截然不同的原因。也就是说,在文字的情况下,字符串对象可以简单地保留该指针,而无需复制,这不仅提供了效率,还提供了安全性(正确性),例如例外。 const char 的数组是我们在 C++11 及更高版本中最清楚的文字指示。

但是,std::string 无法做到这一点:它不是为此而设计的。


如果经常这样做,那么可以定义这样的函数:

using Size = ptrdiff_t;

template< Size n >
auto string_from_data( char const (&data)[n] )
    -> std::string
{ return std::string( data, data + n ); }

那么就可以写

string const s = string_from_data( "*.com[=12=]*.exe[=12=]*.bat[=12=]*.cmd[=12=]" );

免责声明:none 编译器接触或看到的代码。


[我在第一次写作时错过了这个,但 提醒了我。现在去喝咖啡!]

C++14 字符串类型字面量切断了最后的 [=18=],因此对于这样的字面量,上面的代码必须显式地包含终止空值:

string const s = "*.com[=13=]*.exe[=13=]*.bat[=13=]*.cmd[=13=][=13=]"s;

除此之外,C++14 字符串类型文字似乎提供了所寻求的便利。

C++14 引入了字符串文字的后缀,使它们成为 std::string 对象,因此主要用例不再相关。

#include <iostream>
#include <string>
using namespace std;
using namespace std::literals;

int main() {
    string foo = "Hello[=10=]world\n";
    string bar = "Hello[=10=]world\n"s;
    cout << foo.size() << " " << bar.size() << endl; // 5 12
    cout << foo << endl; // Hello
    cout << bar << endl; // Helloworld
    return 0;
}