为什么不允许 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;
}
是否有任何特定的技术原因导致标准未考虑这种方法,而是嵌入 NUL
s 的字符串文字在用于初始化 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;
}
在 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;
}
是否有任何特定的技术原因导致标准未考虑这种方法,而是嵌入 NUL
s 的字符串文字在用于初始化 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;
}