通过指向const对象的指针的引用传递



我一直在做一个项目,使用了大量的双指针,我发现我得到了一些bug。在花了一些时间深入研究之后,我意识到问题是当你通过一个指向const对象的指针的引用传递一个非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

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

更深层次的原因是c++ 17标准中使用的措辞。相关部分([dcl.init.ref])使用了短语"与"相同的类型。int const *int *不属于同一类型。在一个类型的最高级别有不同的简历资格,但在更深的级别没有。因此,在您的情况下,标准实际上需要创建一个临时对象。(过去式是有意的)

幸运的是,这种情况被认为是不希望出现的,正如缺陷报告2352中所记录的那样。提议的决议是将"同类型"改为"同类型"。To"相似",这确实说明了指向类型的cv资格。还有其他措辞上的变化。当我读到它的时候,其他的变化要么是切换到"相似"的结果;或者整理短语以减少将来出现缺陷的机会。也许其他的变化比我意识到的更重要,但重要的是,有了这些变化,在你的情况下不再需要临时工了。

缺陷的解决方案被合并到GCC 10和clang 10中,但没有合并到更早的版本中。(我不知道MSVC的哪个版本(如果有的话)有这种增强。)旧的编译器会给出你的结果(不同的地址),而新的编译器不会。认为这是升级的理由吗?:)


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

在像

这样的例子中
int *ptr;
const int *const &f() {
return ptr;
}

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

你的问题把我难住了,所以我调查了一下。

基本上可以归结为下面的代码来说明:

#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)

对我来说,这里的相关区别在于,在引用绑定期间,如果引用的类型和赋值表达式的类型不相同,则可能涉及">在必要时实现临时的";(https://en.cppreference.com/w/cpp/language/implicit_conversion#Temporary_materialization),这显然是发生在这里。

我链接的cppreference的不同相关部分确实相互交叉引用,所以很难完全确定我没有错过什么,但它似乎解释了行为。

最新更新