尝试深入研究诸如this question之类的案例背后的机制后,我仍然不明白为什么下面代码中的第三行仅生成警告而第二行是错误
int main()
{
const char* const& a = "bla"; // Valid code
const char*& a2 = "bla"; // Invalid code
char* const& a3 = "bla"; // Should be invalid but settles for a warning
return 0;
}
我知道,当引用初始化将字符串文字转换为指针引用时,它不应删除任何 cv限定符对象具有,并且由于转换后的类型为const char* const
(从字符串文字"bla"
,即const char[4]
转换而来),似乎与第二行的情况相同。唯一的区别是被删除的const
属于C字符串本身,而不是指针。
在GCC 8.2和Clang 6.0.0上均进行复制,而未指定任何额外的一致性标志。
gcc 的输出:
<source>:4:23: error: cannot bind non-const lvalue reference of type 'const char*&' to an rvalue of type 'const char*'
const char*& a2 = "Some other string literal";
^~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:5:23: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
char* const& a3 = "Yet another string literal";
为什么当今的编译器符合第一种情况,却不符合第二种情况?或者,这两种情况之间我是否缺少基本的区别?
答案 0 :(得分:10)
字符串文字是数组。 "bla"
的类型为const char [4]
。
const char* const& a = "bla";
这是有效的,因为存在从T []
到T *
的转换;在这种情况下,您会得到一个const char *
右值。此右值可以绑定到引用,因为它是对const
的引用(使临时对象保持活动状态,等等)。
const char*& a2 = "bla";
无效,因为您要在此处尝试将临时值绑定到非常量引用。
char* const& a3 = "bla";
这是对const的引用,但类型错误(它是指向char的指针,而不是指向const char的指针)。此转换会删除const
限定符,因此它应该无效。一些C ++编译器出于向后兼容的原因而允许这样做:在C字符串文字中,它具有非const限定的类型(即"bla"
将是char [4]
),因此使其成为硬错误将破坏很多现有的错误。代码。
即使在C ++中,这过去也是合法的。在C ++ 11之前,仍然允许(但不建议使用)将字符串文字分配给char *
(而不是const char *
)变量。
“双重标准”是因为从未允许将非常量引用绑定到临时(C甚至没有引用),因此那里不存在向后兼容性问题。该标准没有区分“错误”和“警告”。对于任何给定的违反规则,编译是否应由编译器作者决定。
答案 1 :(得分:3)
这两种情况都是不正确的。但是,该标准不要求编译器拒绝格式错误的程序。因此,解决警告完全符合标准。
或者,这两种情况之间我是否缺少基本的区别?
主要区别在于,从未将非常量左值引用绑定到右值,而在C ++ 11之前将const char*
隐式转换为char*
以前是格式良好的。向后兼容是允许后者的一个好理由。
答案 2 :(得分:2)
让我们使用EAST const语法对此进行解构。
const的规则是,它总是适用于它左边的内容,除非它左边没有什么,在这种情况下,它适用于右边的内容。使用EAST const,我们总是在右边写const。
所以让我们看一下代码:
const char* const& a = "bla"; // Valid code
成为
char const * const & a = "bla";
所以char是常量,不能更改。
指向字符的指针是恒定的,也不能更改。
总体:这是对不能更改为不能更改的字符的指针的引用。
“ bla”是const C样式的数组,可立即衰减为char const * const。
之所以使用“ char const * const”而不是“ char const *”是因为“ bla”的地址是恒定的-字符串“ bla”被编译到执行代码中固定位置的某个位置,以及何时装入程序的内存将一直保留在该内存地址,直到程序终止。
所以现在我们有了匹配类型(引用除外)。
T&a =某物;如果某物的类型为T并且该物具有地址(它确实有地址),它将始终有效。
让我们看看第二个:
const char*& a2 = "bla";
EAST const语法:
char const * & a2 = "bla";
“ bla”的类型:
char const * const
这些类型不匹配(“ bla”的存储位置是固定的)。
也许这段代码会更清楚:
char const *stringPtr = "hello";
char const *stringPtr2 = "world";
char const * &stringPtrRef = stringPtr;
std::cout << stringPtr << std::endl;
stringPtrRef = stringPtr2;
std::cout << stringPtr << std::endl;
这将在第一行中打印“ Hello”,在第二行中打印“ World”。这是因为stringPtr指向的更改。
由于“ bla”的位置是固定的,因此无法通过将其引用设置为其他内容来构造对它的引用。只是不可能。我们也没有办法强制我们将其强制转换为正确的类型。
这就是为什么即使有警告也无法编译的原因。
让我们看看第三个:
char* const& a3 = "bla";
这已经是EAST const格式。
带有“ char * const&”-结果引用,虽然不允许更改内存位置,但可以将“ bla”修改为“ abc”。
也许在某些情况下,您实际上想这样做是为了节省某些仅将“ bla”用作初始化而不再使用的嵌入式系统上的内存空间。
该消息很有意义:
“警告:ISO C ++禁止将字符串常量转换为'char *”
因为这与以下内容基本相同:
char const *s1 = "bla";
char *s2 = s1;
实际上会在编译时带有警告,并带有正确的编译器标志(-fpermissive)。
即使没有-fpermissive,我们也可以更改代码以进行强制转换并使之正常工作。
因此,我知道为什么可以编译,但是我认为这应该是一个错误。 ISO C ++明确禁止这样做。我的看法:如果这确实是您真正想要做的,则需要强制转换。