繁体   English   中英

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

[英]Dangers of using reference-to-pointer as function argument

有人可以告诉我这是否安全,因为我认为它不是:

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)有效。 我们正在传递一个指针,function 接受它作为参考。

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

但是为什么 (3) 会编译呢? 这是否意味着我们允许无效引用,所以b.m_i本质上是未定义的?

我的工作原理完全错了吗? 我问是因为我得到奇怪的单元测试失败,我只能通过指针变得无效来解释。 它们只发生在某些编译器上,所以我假设这一定是标准之外的东西。

所以我的核心问题基本上是:在 function 参数中使用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*变量,所以满足引用。 并且ja1寿命长,因此A::m_i成员在a1的生命周期内可以安全使用。

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

B b(&i); 编译,因为B的构造函数引用了一个 const int* ,它可以绑定到一个临时的。 临时对象的生命周期将通过绑定到构造函数的i参数来延长,但是一旦构造函数退出就会过期,因此B::m_i成员将是一个悬空引用,并且在构造函数退出后根本无法安全使用。

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

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

&i是一个纯右值(临时),它可以绑定到一个 const 左值引用。 将纯右值绑定到引用会将临时对象的生命周期延长到引用的生命周期。 在这种情况下,这个临时生命周期被延长到构造函数参数i的生命周期。 然后将引用m_i初始化为i (构造函数参数)(这是对临时的引用),但因为i是左值,临时的生命周期不会延长。 最后你会得到一个引用成员m_i绑定到一个不存在的 object 。 你有一个悬空的参考 从现在开始(在构造函数完成后)访问m_i是未定义的行为。


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

指针是一个 memory 地址。 为简单起见,将指针视为uint64_t变量,其中包含一个代表 memory 地址的数字。 引用只是某些变量的别名。

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

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

在示例 (3) 中,您将一个数字值传递给构造函数,期望对指针进行常量引用。 常量引用是一个固定的数字,只是一个 memory 地址。 在这种情况下,编译器理解意图并提供 memory 地址以在构造函数中设置。 结果,您获得了i的固定别名。

编辑(为清楚起见):(2)和(3)之间的区别在于&i不是对int*的有效引用,但它是对int*的有效const引用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM