谁能告诉我这样做是否安全,因为我觉得不安全。
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
是一个r值,没有自己的记忆,所以引用不能有效。我可以接受这个解释(如果这是真的)。
但是为什么(3)会编译呢?这岂不是意味着我们允许无效的引用,所以 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
}
然而,由于b不能知道j已经被修改,它似乎持有一个对 "个人 "指针的引用,但我担心的是,它在标准中没有很好地定义,所以可能有一些编译器对这个 "个人 "指针没有定义。谁能证实这一点?
A
的构造函数取一个 非宪法参照物 到一个 int*
指针。 A a1(j);
工作,因为 j
是一个 int*
变量,所以满足引用。 而 j
寿终正寝 a1
所以 A::m_i
成员的一生都可以安全使用。a1
.
A a2(&i);
编译失败,因为虽然 &i
是一个 int*
, operator&
返回一个 暂时 值,它不能被绑定到一个非const引用上。
B b(&i);
编译时,因为 B
的构造函数取一个 参考 变成 int*
,它可以被绑定到一个临时变量上。 临时的寿命将通过绑定到构造函数的 i
参数,但一旦构造函数退出,该参数就会失效,因此 B::m_i
成员将是一个 悬指 并且在构造函数退出后根本无法安全使用。
j
是一个lvalue,因此它可以被绑定到一个非const lvaue引用上。
&i
是一个prvalue,因此它不能被绑定到非const lvalue引用上。这就是为什么(2)不能编译的原因。
&i
是一个 prvalue(临时),它可以被绑定到一个 const lvalue 引用上。将一个prvalue绑定到一个引用上,可以将临时的寿命延长到引用的寿命。在这种情况下,这个临时的寿命被延长到构造函数参数的寿命。i
. 然后您可以初始化参考文献 m_i
到 i
构造参数)(这是一个对临时的引用),但由于 i
是一个l值,临时成员的寿命不会被延长。最后,你会得到一个引用成员 m_i
绑定到一个没有生命的对象上。你有一个 悬指. 访问 m_i
从现在开始(在构造函数完成后)是未定义行为。
简单的表,可以绑定到什么引用。C++11 rvalue reference vs const reference
指针是一个内存地址。为了简单起见,可以把指针看成是 uint64_t
变量,持有一个代表什么的内存地址的数字。引用只是某个变量的别名。
在例子(1)中,你将一个指针传递给构造函数,期望得到一个对指针的引用。它的工作原理是,编译器得到指针值所在的内存地址,并将其传递给构造函数。构造函数得到这个数字并创建一个别名指针。结果你得到的是一个别名为 j
. 如果您修改 j
那么 m_i
也会被修改。您可以修改 m_i
也指向其他东西。
在例子(2)中,你向构造函数传递了一个数字值,期望得到一个指向指针的引用,因此,构造函数得到的不是一个地址,而是一个地址,编译器没有办法满足构造函数的签名。所以,构造函数得到的不是一个地址的地址,而是一个地址,编译器没有办法满足构造函数的签名。
在例子(3)中,你传递了一个数字值给构造函数,期望得到一个恒定的指针引用。恒量引用是一个固定的数字,只是一个内存地址。在这种情况下,编译器理解了这个意图,并提供了在构造函数中设置的内存地址。结果你得到了固定的别名的 i
.
编辑(为清楚起见)。(2)和(3)之间的区别是: &i
不是对 int*
但它是一个有效的 常设 提及 int*
.