有人能告诉我这是否安全吗,因为我认为不是:
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);
起作用,因为j
是int*
变量,所以满足引用。并且j
的寿命比a1
的寿命长,因此A::m_i
成员在a1
的使用寿命内是安全的。
A a2(&i);
编译失败,因为尽管&i
是int*
,但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*
的有效常量引用。