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

时间:2015-11-17 07:24:51

标签: c++ arrays string string-literals

在C ++中,您可以从std::stringchar *初始化const char *对象,这隐含地假设字符串将在指针后找到的第一个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]=='\0')) {}

    // 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\0world\n";
    printf("Length s1 -> %i\n", s1.size());
    const char *s2 = "Hello\0world\n";
    printf("Length s2 -> %i\n", String(s2).size());
    std::string s3 = "Hello\0world\n";
    printf("std::string size = %i\n", int(s3.size()));
    return 0;
}

是否有任何特定的技术原因,对于标准没有考虑这种方法,而是在用于初始化NUL对象时,嵌入std::string的字符串文字会被截断?< / p>

2 个答案:

答案 0 :(得分:4)

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

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

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

答案 1 :(得分:2)

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

如果有专门的take-array-reference构造函数模板,这是最简单的,但正如你所注意的那样

  • 这样的模板,只有 数组参数,被认为是比构造函数更简单的匹配char const*

  • 目前尚不清楚是否应包括最终的终止零值。

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

第二点是引入错误的机会。毫无疑问,构造函数最常见的用法是普通的字符串文字。然后,它会偶尔用于带有嵌入式nullbytes的文字和/或数组,但奇怪的是最后一个字符被砍掉。

相反,人们可以简单地命名该值,

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

所有这一切,当我已经定义了我自己的字符串类时,我已经包含了take-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\0*.exe\0*.bat\0*.cmd\0" );

免责声明:编译器未触及或看到任何代码。

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

C ++ 14字符串类型文字会从最终的\0中删除,所以对于这样的文字,上面必须包括明确终止nullvalue:

string const s = "*.com\0*.exe\0*.bat\0*.cmd\0\0"s;

除此之外,C ++ 14字符串类型文字似乎提供了方便的搜索。