使用对指针的引用作为函数参数的危险



有人能告诉我这是否安全吗,因为我认为不是:

class A
{
public:
A(int*& i) : m_i(i)
{}
int*& m_i;
};
class B
{
public:
B(int* const& i) : m_i(i)
{}
int* const & m_i;
};

int main()
{
int i = 1;
int *j = &i;
A a1(j);   // this works (1)
A a2(&i);  // compiler error (2)
B b(&i);   // this works again (3)
}

我理解为什么(1)有效。我们正在传递一个指针,函数接受它作为引用。

但为什么(2)不起作用呢?从我的角度来看,我们传递的是同一个指针,只是没有先将其分配给指针变量。我的猜测是&i是一个右值,并且没有自己的内存,所以引用不可能有效。我可以接受那个解释(如果是真的)。

但是为什么要编译呢?这难道不意味着我们允许无效引用,所以b.m_i本质上是未定义的吗?

我是不是完全错了?我之所以这么问,是因为我遇到了奇怪的单元测试失败,我只能通过指针无效来解释。它们只在某些编译器中发生,所以我认为这一定是标准之外的东西。

因此,我的核心问题基本上是:在函数参数中使用int* const &是否本质上是危险的,应该避免,因为毫无戒心的调用者可能总是像使用常规指针参数一样使用&i来调用它?

附录:正如@franji1所指出的,以下是一个有趣的想法,可以理解这里发生了什么。我修改了main()来更改内部指针,然后打印成员m_i:

int main()
{
int i = 1;
int *j = &i;  // j points to 1
A a1(j);
B b(&i);
int re = 2;
j = &re;  // j now points to 2
std::cout << *a1.m_i << "n";  // output: 2
std::cout << *b.m_i << "n";  // output: 1
}

因此,显然a1按预期工作。然而,由于b不知道j已经被修改,它似乎引用了一个"个人"指针,但我担心的是,它在标准中没有得到很好的定义,因此可能有编译器没有定义这个"个人"指示器。有人能证实吗?

A的构造函数接受对int*指针的非常量引用A a1(j);起作用,因为jint*变量,所以满足引用。并且j的寿命比a1的寿命长,因此A::m_i成员在a1的使用寿命内是安全的。

A a2(&i);编译失败,因为尽管&iint*,但operator&返回一个临时值,该值不能绑定到非常量引用。

B b(&i);编译,因为B的构造函数对常量int*进行引用,该常量可以绑定到临时的。临时成员的生存期将通过绑定到构造函数的i参数来延长,但一旦构造函数退出就会过期,因此B::m_i成员将是悬挂引用,在构造函数退出后根本无法安全使用。

j是一个左值,因此它可以绑定到一个非常量的lvaue引用。

&i是一个prvalue,它不能绑定到非常量左值引用。这就是(2)不编译的原因

&i是一个prvalue(临时),它可以绑定到const lvalue引用。将prvalue绑定到引用将临时的生存期扩展到引用的生存期。在这种情况下,这个临时生存期被扩展到构造函数参数i的生存期。然后将引用m_i初始化为i(构造函数参数)(这是对临时的引用),但由于i是一个左值,因此临时的生存期不会延长。最后,您会得到一个绑定到不活动对象的引用成员m_i。您有一个悬空引用。从现在起(在构造函数完成之后)访问m_i是Undefined Behavior。


引用可以绑定到什么的简单表:C++11右值引用与常量引用

指针是一个内存地址。为了简单起见,可以将指针想象成uint64_t变量,该变量包含一个表示任意内存地址的数字。引用只是某个变量的别名。

在示例(1)中,您将一个指针传递给构造函数,该构造函数需要对指针的引用。它按预期工作,因为编译器获取存储指针值的内存地址,并将其传递给构造函数。构造函数获取该数字并创建一个别名指针。因此,您得到了一个别名j。如果修改j以指向其他内容,则m_i也将被修改。您也可以修改m_i以指向其他内容。

在示例(2)中,您将一个数值传递给构造函数,该构造函数需要对指针的引用。因此,构造函数获取的不是地址的地址,而是地址,编译器无法满足构造函数的签名。

在示例(3)中,您将一个数值传递给构造函数,该构造函数需要对指针的常量引用。常量引用是一个固定的数字,只是一个内存地址。在这种情况下,编译器理解意图,并提供要在构造函数中设置的内存地址。因此,您得到了i的固定别名。

EDIT(为清楚起见):(2)和(3)的区别在于,&i不是对int*的有效引用,但它是对int*的有效常量引用。

最新更新