为什么 const T*& 不能绑定到 T*?

5

VS2010 显示错误 C2440: 下面的 f3'return' : 无法将类型为 'char *' 的返回值转换为类型为 'const char *&' 的返回值。 为什么当返回类型为 const char*&时不能返回char*呢?

const char* const& f1()
{
    static char* x = new char[5]();
    return x;
}

char* const& f2()
{
    static char* x = new char[5]();
    return x;
}

const char* & f3()
{
    static char* x = new char[5]();
    return x;    // error C2440
}

char* & f4()
{
    static char* x = new char[5]();
    return x;
}
3个回答

5

来自C++11标准第8.5.3/2段:

初始化后,引用不能更改为指向另一个对象。请注意,引用的初始化与对其进行赋值处理非常不同。参数传递(5.2.2)和函数返回值(6.6.3)是初始化

这基本上告诉你,从函数返回一个值等同于执行一个初始化。因此,函数f3()无法编译,原因与下面代码片段中的最后一个初始化无法编译相同:

char c = 'a';
char* x = &c;
const char*& y = x; // ERROR!
y所引用的对象类型为const char*,而我们试图用其初始化的表达式(即x的类型)的类型为char*。这些是不同的类型,并且在绑定引用时,初始化引用的表达式和所引用的对象的类型必须相同(除了基类和派生类,这里没有涉及),除了顶层cv限定符之外
在这里,const char*中的const限定符不是顶层限定符,因为它适用于指向的对象,而不是指针本身:虽然不能通过const char*指针修改所指向的char值,但指针本身可以被重新分配。
按照标准术语,这意味着类型const char*char*不是相关的引用类型
给定类型“cv1 T1”和“cv2 T2”,当 T1T2 的类型相同时,或者 T1T2 的基类时,“cv1 T1”与“cv2 T2”是引用相关的。“cv1 T1”与“cv2 T2”是引用兼容的,如果T1T2引用相关的,并且cv1的修饰符与cv2相同或更高。[...]

另一方面,函数f2()编译通过,这与以下最后一行中的初始化合法相一致:

char c = 'a';
char* x = &c;
char* const& y = x; // OK

这里引用对象的类型是char* const,与上述段落定义的意义相同,它(不像const char*)与char*在引用兼容性方面是可以互换的。在这种情况下,const资格证书是一个顶层资格证书。

特别地,在8.5.3/5中:

对“cv1 T1”类型的引用通过以下方式从“cv2 T2”类型的表达式初始化:

— 如果引用是左值引用且 初始化器表达式

  • 是一个左值(但不是一个位域),并且“cv1 T1”与“cv2 T2”的引用兼容,或者

[...]

被省略的部分与此案件无关。在这里,char* constchar*引用兼容的,并且初始化是合法的(并且f2()编译)。另一方面,const char*char*不是引用兼容的,初始化是非法的(并且f3()不编译)。

什么是“顶级”CV资格?在这个背景下,你如何描述“顶级”这个术语? - Belloc
@user1042389:我来举个例子:在 int constint* const 中,const 是顶层的;在 int const*(与 const int* 相同)中,const 不是顶层的,因为它应用于指向的 int,而不是指针本身。一般来说,在 C++ 中,您可以通过“指向”、“引用”、“数组”等构造来构建复合类型。当您以这种方式组成类型时(例如,“指向 char 的指针的指针的引用”),您的类型具有分层结构(在我们的情况下是链)。顶层限定符是应用于此结构的顶级节点的限定符。 - Andy Prowl

2
由于同样的原因,你不能将char **转换为const char **。否则你可以写成:
f3() = "hello";

接着调用f3函数,就能够写入字符串常量的内存,该字符串常量别名为静态局部变量 x


嗯,为什么f3() =“hello”可以编译通过?f3()是const的吗?还是像在const int&i = b的声明中一样计数? - qPCR4vir
f3() = "hello" 可以编译通过,因为你的特定编译器出于遗留原因允许你做一些愚蠢的事情。许多 C API 具有 char* 接口,它们承诺不会写入。因此,通过默默地将字符串字面值转换为 char*,C API 仍然可以正常工作。 - Yakk - Adam Nevraumont

2
#include <iostream>
void problem( const char*& output, const char* input )
{
  output = input; // legal, const char*& = const char*
}

int main() {
  const char buff[] = "hello";
  char* out = nullptr;
  problem( const_cast<const char*&>(out), &buff[0] );
  out[0] = 'H'; // I just changed a const array's first character
  std::cout << &buff[0] << "\n";
}

这段代码打印出“Hello”,这是我刚刚执行未定义行为的证据。为了这样做,我必须使用const_cast。如果你可以用char*初始化const char*&,那么我就不必使用const_cast来调用未定义行为。
但是,有人可能会说:“这与OP发布的确切情况不符!”
这是真的。
// the "legal" way to do what the OP wants to do, but it leads to undefined
// behavior:
const char*& problem2( char*& c ) { return const_cast<const char*&>(c); }
void problem3( const char*& left, const char* right ) { left = right; }

#include <iostream>
int main() {
  char* c = nullptr;
  char const* buff = "hello undefined behavior";
  problem3( problem2(c), buff );
  c[0] = 'H';
  std::cout << buff << "\n";
}

还会输出 "Hello undefined behavior"。

但是,有人可能会说:"你把 char*& 转换成了 const char*&,而不是将 char* 转换成 const char*&

好的:

void problem3( const char*& left, const char* right ) { left = right; }
const char*& problem4( char x = 0 ) {
  static char* bob = nullptr;
  if (bob && x)
    bob[0] = x;
  return const_cast<const char*&>(bob);
}
#include <iostream>
int main() {
  char const* buff = "hello problem";
  problem3( problem4(), buff );
  problem4('H');
  std::cout << buff << "\n";
}

再次提到那个罪状明显的大写字母H(实际上,这是通常表现为大写字母H的未定义行为)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接