为什么std::string没有(显式)const char*强制转换

Why std::string does not have (explicit) const char* cast

本文关键字:char 转换 const 显式 string std 没有 为什么      更新时间:2023-10-16

我想知道赞成和反对是否有这样的演员阵容。在包括Stack Overflow在内的几个地方,我可以看到const char*强制转换被认为是个坏主意,但我不确定为什么?

在编写通用例程和模板时,缺少(const char*)并强制始终使用c_str()强制转换会产生一些问题。

void CheckStr(const char* s)
{
}
int main()
{
std::string s = "Hello World!";
// all below will not compile with 
// Error: No suitable conversion function from "std::string" to "const char *" exists!
//CheckStr(s);             
//CheckStr((const char*)s);  
// strlen(s);
// the only way that works
CheckStr(s.c_str());
size_t n = strlen(s.c_str());
return 0;
}

例如,如果我有大量接受const char*作为输入的文本处理功能,并且我希望每次必须使用c_str()时都能够使用std::string。但这样一来,如果不付出额外的努力,模板函数就不能同时用于std::stringconst char*

作为一个问题,我可以看到一些操作员过载的问题,但这些都是可以解决的。

例如,正如[eerorika]所指出的,通过允许隐式转换为指针,我们允许字符串类非自愿地参与布尔表达式。但是我们可以很容易地通过删除布尔运算符来解决这个问题。更进一步,可以强制铸造操作员显式:

class String
{
public:
String() {}
String(const char* s) { m_str = s; }
const char* str() const  { return m_str.c_str(); }
char* str()  { return &m_str[0]; }
char operator[](int pos) const { return m_str[pos]; }
char& operator[](int pos) { return m_str[pos]; }
explicit operator const char*() const { return str(); }  // cast operator
operator bool() const = delete;
protected:
std::string m_str;
};
int main()
{
String s = "Hello";
string s2 = "Hello";
if(s)  // will not compile:  it is a deleted function
{
cout << "Bool is allowed " << endl;
}
CheckStr((const char*)s);
return 0;
}

我想知道有没有这样的演员阵容。

Con:隐式转换的行为常常让程序员感到惊讶。

例如,您对以下程序的期望是什么?

std::string some_string = "";
if (some_string)
std::cout << "true";
else
std::cout << "false";

程序是否应该因为std::string没有转换为bool而格式错误?结果应该取决于字符串的内容吗?大多数程序员会有同样的期望吗?

在电流std::string的情况下,由于不存在这样的转换,所以上述内容将不成形。这很好。无论程序员期望什么,当他们试图编译时,他们都会发现自己的误解。

如果std::string有一个到指针的转换,那么也会有一个通过到指针的转化到bool的转换序列。上面的程序会很好地形成。程序将打印true,而不管字符串的内容如何,因为c_str从不为空。若程序员期望空字符串为false,该怎么办?如果他们从来没有打算采取任何一种行为,而是意外地在那里使用了一根绳子呢?

下面的节目怎么样?

std::string some_string = "";
std::cout << some_string + 42;

由于字符串和int没有这样的运算符,您会认为程序格式不正确吗?

如果存在到char*的隐式转换,则上述操作将具有未定义的行为,因为它执行指针运算并在其边界之外访问字符串缓冲区。


// all below will not compile with 
strlen(s);

这实际上是一件好事。大多数时候,您不想拨打strlen(s)。通常,您应该使用s.size(),因为它是渐进的更快。对strlen(s.c_str())的需求是如此之少,以至于一点点的冗长都是微不足道的。

强制使用.c_str()非常好,因为它向程序的读取器表明,传递给函数/运算符的不是std::string,而是char*。使用隐式转换,不可能区分两者。


。。。在编写通用例程和模板时会产生一些问题。

这样的问题并非不可逾越。

如果"具有强制转换"是指用户定义的转换运算符,那么它没有强制转换运算符的原因是:防止您隐式使用它,可能是无意中使用它。

从历史上看,无意中使用这种转换的不良后果源于这样一个事实,即在最初的std::string(根据C++98规范)中,操作是重型危险

  • 最初的std::string不能简单地转换为const char *,因为字符串对象最初并不打算/需要存储null终止符。在这种情况下,转换为const char *是一个潜在的操作,通常分配一个独立的缓冲区并将整个受控序列复制到该缓冲区。

  • 上面提到的独立缓冲区(如果使用)具有潜在的"意外"寿命。对原始std::string对象的任何修改操作都会触发该缓冲区的无效/解除分配,从而使以前返回的指针无效。

实现隐式调用转换运算符这样繁重而危险的操作从来都不是一个好主意。

最初的C++标准(C++98)没有explicit转换运算符这样的特性。(它们最早出现在C++11中。)一个专用的命名成员函数是在C++98中以某种方式使转换显式的唯一方法。

今天,在现代C++中,我们可以定义一个转换运算符,但仍然可以防止它被隐式使用(通过使用explicit关键字)。有人可以说,在这种情况下,由运营商实施转换是一种合理的方法。但我仍然认为这不是一个好主意。即使现代的std::string需要存储其空终止符(即c_str()不再产生独立的缓冲区),转换为const char *返回的指针仍然是"危险的":应用于std::string对象的许多修改操作可能(也将)使该指针无效。为了强调这不仅仅是一个安全和无辜的转换,而是一个产生潜在危险指针的操作,通过命名函数实现它是非常合理的。