【问题标题】:Passing by reference of a pointer to a const object通过指向 const 对象的指针的引用传递
【发布时间】:2021-06-04 10:28:50
【问题描述】:

我一直在从事一个使用大量双指针的项目,但我发现我遇到了一些错误。在花了一些时间研究它之后,我意识到问题是当你通过一个指向 const 对象的指针的引用传递一个非常量对象时,它最终会通过副本而不是引用传递。我不明白为什么会这样,因为引用只是一个别名,如果您尝试更改该范围内的对象的某些内容,则 const 部分应该只会导致错误。为了进一步混淆,当传递 const 对象的双指针或它只是对 const 对象的引用时,不会发生这种情况。谁能解释为什么会发生这种情况以及这个特殊案例与我所包含的其他案例有何不同?

#include <iostream>

void PassByConstPtrRef(int const *const &num_ptr_ref)
{
    std::cout << &num_ptr_ref << std::endl;
}

void PassByPtrRef(int *const &num_ptr_ref)
{
    std::cout << &num_ptr_ref << std::endl;
}

void PassByPtrPtr(int const *const *const num_ptr_ref)
{
    std::cout << num_ptr_ref << std::endl;
}

void PassByConstRef(int const &num)
{
    std::cout << &num << std::endl;
}

int main()
{
    int *num_ptr = new int{ 10 };
    int *const *num_ptr_ptr = &num_ptr;
    int *const &num_ptr_ref = *num_ptr_ptr;

    std::cout << num_ptr_ptr << " : " << &num_ptr_ref << std::endl; // is equal

    std::cout << num_ptr_ptr << " : ";
    PassByConstPtrRef(num_ptr_ref); // is not equal

    std::cout << num_ptr_ptr << " : ";
    PassByPtrRef(num_ptr_ref); // is equal

    std::cout << num_ptr_ptr << " : ";
    PassByPtrPtr(&num_ptr_ref); // is equal

    int foo = 4;
    int &bar = foo;

    std::cout << &foo << " : ";
    PassByConstRef(bar); // is equal
}

输出:

0x7ffeeb6d59c8 : 0x7ffeeb6d59c8

0x7ffeeb6d59c8 : 0x7ffeeb6d59b0

0x7ffeeb6d59c8 : 0x7ffeeb6d59c8

0x7ffeeb6d59c8 : 0x7ffeeb6d59c8

0x7ffeeb6d59ac : 0x7ffeeb6d59ac

【问题讨论】:

  • @churill 谢谢,我已经在底部添加了输出。
  • @JaMiT 我很高兴收到对我的猜测的更正,如果你有的话?它可能会让我不那么困惑。在我来这里提问之前,我只是想自己弄清楚,所以我想我应该分享一下我在哪里。
  • @JaMiT 不会让我的代码适用于 int 但不适用于类吗?它似乎在这两种情况下都不起作用,我通过删除 const 让我的原始代码正常工作。
  • 啊...我看到了混淆的一个原因。结果在 gcc 9 和 gcc 10 之间以及 clang 9 和 clang 10 之间发生了变化。在 10+ 版本中进行测试的人不会看到差异。
  • @JaMiT 是的,这正是我的问题。 MSVC 也在产生 OPs 结果。这是一个test on godbolt 供参考,显示了不同版本的2 个编译器。

标签: c++ pointers reference constants double-pointer


【解决方案1】:

你的问题让我很困惑,所以我调查了一下。

基本上可以归结为这段代码进行说明:

#include <iostream>

int main()
{
    int *num_ptr = new int{ 10 };
    int *const &num_ptr_ref = num_ptr;
    int *const *num_ptr_ptr = &num_ptr;

    // Using a reference
    int const *const &const_num_ptr_ref = num_ptr_ref;
    std::cout << num_ptr_ptr << " : " << &const_num_ptr_ref << std::endl;;

    // Using a pointer    
    int const *const *const const_num_ptr_ptr = num_ptr_ptr;
    std::cout << num_ptr_ptr << " : " << const_num_ptr_ptr << std::endl;;
}

https://www.onlinegdb.com/online_c++_compiler上尝试,可以看到前2个地址不同,后2个地址相同。

经过一番挖掘,这是我对情况的理解:

  1. 第一个赋值是引用初始化 (https://en.cppreference.com/w/cpp/language/reference_initialization)
  2. 第二个赋值是多级指针限定转换(https://en.cppreference.com/w/cpp/language/implicit_conversion#Qualification_conversions)

与我相关的不同之处在于,在引用绑定期间,如果引用的类型和分配的表达式不相同,则可能涉及“如有必要,实现一个临时的”(@987654324 @),这显然似乎发生在这里。

我链接的 cppreference 的不同相关部分确实相互交叉引用,因此很难完全确定我没有错过任何东西,但它似乎可以解释这种行为。

【讨论】:

    【解决方案2】:

    这是一个令人困惑的问题。症状的直接原因是为了将num_ptr_ref 传递给PassByConstPtrRef(),创建了一个临时对象。绑定到这个临时的参数,因此具有不同的地址。虽然这种现象在处理对象时是(有点)已知的危险,但当唯一的区别是更严格的 cv 限定(“cv”是“const-volatile”的缩写)时,指针会出现这种情况似乎很奇怪。毕竟数据是一样的;不同之处在于可以对数据做什么。

    造成这种情况的更深层原因是 C++17 标准中使用的措辞。相关部分 ([dcl.init.ref]) 使用了短语“same type as”并且int const *int * 不同类型。在一个类型的顶层允许不同的 cv-qualification,但在更深的层次上没有。因此,该标准实际上需要在您的情况下创建一个临时文件。 (过去时是故意的。)

    幸运的是,这种情况被认为是不可取的,如 defect report 2352 中所述。提议的解决方案是将“相同类型”更改为“相似”,这确实说明了指向类型的 cv 限定。还有其他措辞变化。当我读到它时,其他变化要么是切换到“相似”的结果,要么是整理措辞以减少未来出现缺陷的机会。也许其他更改比我意识到的更重要,但重要的是,通过这些更改,您的情况不再需要临时更改。

    该缺陷的解决方案已纳入 GCC 10 和 clang 10,但未纳入早期版本。 (我不知道 MSVC 的哪个版本(如果有)具有此增强功能。)较旧的编译器会给出您的结果(不同的地址),而较新的编译器不会。考虑这是升级的理由吗? :)


    作为参考,这里是缺陷报告中给出的示例。它使用返回值而不是参数,但原理是一样的。有一个指向int 的指针和一个指向const int 的(const) 指针的引用,并且该引用不能直接绑定到指针。

    举个例子

    int *ptr;
    const int *const &f() {
      return ptr;
    }
    

    返回的是对临时的引用,而不是直接绑定到ptr

    【讨论】:

    • 读起来有点难,但我想我已经掌握了要点。感谢您为我指出正确的资源的解释。
    • @JoshuaWilliams 我终于回到了这个话题。答案现在应该更容易阅读了。 (我不是承诺“容易”,而是“更容易”。;))
    猜你喜欢
    • 1970-01-01
    • 2021-02-28
    • 1970-01-01
    • 1970-01-01
    • 2017-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多