【问题标题】:interaction of pointer and const and overload指针与 const 和重载的交互
【发布时间】:2021-01-29 16:33:10
【问题描述】:

正如您将看到的,我对混合指针 const 和重载时会发生什么的理解不是很有信心。因此,在questions such as this one 之后,我尝试在以下代码中明确所有情况。

我的问题是:我是否错过了一些地址、指针、const 指针以及可能相互重载的各种原型之间的交互案例?

//g++  7.4.0
#include <iostream>
using namespace std;

// ---------- which legal overloads ?
/* 
void f_012a( int* pi ){ cout << "pi"; }
//void f_012a( int * const pi ){ cout << "pci"; } //error: redefinition of ‘void f_012a(int*)’
void f_012a( const int * pi ){ cout << "pci"; }
//void f_012a( const int * const  pi ){ cout << "cpci"; } //error: redefinition of ‘void f_012a(const int*)’
*/

// ---------- overloads declaration order impact ?
void f_01( int* pi )       { cout << "pi";  }
void f_01( const int * pi ){ cout << "pci"; }
void f_10( const int * pi ){ cout << "pci"; }
void f_10( int* pi )       { cout << "pi";  }

int main(){
    cout << "/** trace\n";
    {
        cout << "-------- pointers and const\n";
        int* pi                = new int(1);
        int * const pci        = new int(2);
        const int * cpi        = new int(3);
        const int * const cpci = new int(4);
        cout << "pi -> " << *pi  << " / pci -> " << *pci << " / cpi -> " << *cpi << " / cpci -> " << *cpci << "\n"; 

        *pi  = 5;
        cout << "pi -> " << *pi  << " / pci -> " << *pci << " / cpi -> " << *cpi << " / cpci -> " << *cpci << "\n"; 
        pi   = new int(6);
        cout << "pi -> " << *pi  << " / pci -> " << *pci << " / cpi -> " << *cpi << " / cpci -> " << *cpci << "\n"; 

        *pci  = 7;
        cout << "pi -> " << *pi  << " / pci -> " << *pci << " / cpi -> " << *cpi << " / cpci -> " << *cpci << "\n"; 
        //pci   = new int(8); //error: assignment of read-only variable ‘pci’

        //*cpi  = 8;
        cpi   = new int(8);  //error: assignment of read-only location ‘* cpi’
        cout << "pi -> " << *pi  << " / pci -> " << *pci << " / cpi -> " << *cpi << " / cpci -> " << *cpci << "\n"; 

        //*cpci  = 9;          //error: assignment of read-only location ‘* cpi’
        //cpci   = new int(9); //error: assignment of read-only variable ‘cpci’
    }
    
    {
        cout << "-------- pointers, const, and overload\n";
        int i                  = 1;
        int* pi                = new int(2);
        int * const pci        = new int(3);
        const int * cpi        = new int(4);
        const int * const cpci = new int(5);
        cout << " i : " << i << " / pi -> " << *pi  << " / pci -> " << *pci << " / cpi -> " << *cpi << " / cpci -> " << *cpci << "\n"; 
    
        cout << "var\tf_01\tf_10\n"; 
        cout << "&i\t"; f_01(&i); cout<<"\t"; f_10(&i); cout<<"\n"; 
        cout << "pi\t"; f_01(pi); cout<<"\t"; f_10(pi); cout<<"\n"; 
        cout << "pci\t"; f_01(pci); cout<<"\t"; f_10(pci); cout<<"\n"; 
        cout << "cpi\t"; f_01(cpi); cout<<"\t"; f_10(cpi); cout<<"\n"; 
        cout << "cpci\t"; f_01(cpci); cout<<"\t"; f_10(cpci); cout<<"\n"; 
    }
        
    cout << "*/\n";
    return 0;
}

/** trace
-------- pointers and const
pi -> 1 / pci -> 2 / cpi -> 3 / cpci -> 4
pi -> 5 / pci -> 2 / cpi -> 3 / cpci -> 4
pi -> 6 / pci -> 2 / cpi -> 3 / cpci -> 4
pi -> 6 / pci -> 7 / cpi -> 3 / cpci -> 4
pi -> 6 / pci -> 7 / cpi -> 8 / cpci -> 4
-------- pointers, const, and overload
 i : 1 / pi -> 2 / pci -> 3 / cpi -> 4 / cpci -> 5
var f_01    f_10
&i  pi  pi
pi  pi  pi
pci pi  pi
cpi pci pci
cpci    pci pci
*/

【问题讨论】:

  • 从调用者看来参数int *int * const是相等的,你想用int *调用函数。第二个版本只为您提供信息,该函数不会更改您不关心的指针,因为它是您的指针的副本。
  • 您链接的问答中的解释通常适用。除非T 是引用void foo(const T)void foo(T) 声明相同的函数。指针并不像您想象的那么特别,因此不清楚您在寻找什么作为答案。

标签: c++ pointers constants overloading


【解决方案1】:

这里真正的问题是理解关于类型的“从右到左”规则。对const char 等价于char const 的“最左边常量的特殊情况”的存在感到有些困惑。

在 Nicolai Josuttis 的“C++ 模板”一书中,他提出了首选“const right”以避免使用特殊情况的情况,因此从右到左的规则是一致的。另一个倡导者是 Dan Saks 在这个视频“East const but constexpr West”中 - https://www.youtube.com/watch?v=z6s6bacI424

有很多文章解释了从右到左的规则,还有一个cdecl 工具用简单的英语描述了 C 类型。见How do you read C declarations?https://www.codeproject.com/Articles/7042/How-to-interpret-complex-C-C-declarations

还有一个基于web的cdecl:https://cdecl.org/

请注意,通过 valuereference 传递参数之间存在差异。当您通过值传递指向方法的指针时,您实际上是在给它一个副本,并且由方法来声明它是否会改变副本。当您通过引用传递参数时,您是在共享该变量,因此该方法不能改变 const 引用。


编译器的任务是选择合适的重载,链接器根据函数的重命名来连接。这就是重载在二进制上下文中的工作方式。

有趣的是,在 Visual-C++ 编译器中,当值传递参数为 const 时,名称修饰不同的。此示例查看使用指向 int 的指针、指向 int-const 的指针、按值传递(const 和非)、按引用传递(const 和非)的 8 种组合。

请注意,如果方法都被称为f_01,编译器会报错,因为它提供了多个可能的选项并且无法解决歧义,因为您可以将可变值传递给 const-value 方法。


// These do not affect the value of the pointer in the caller
void f_01(int* pi) 
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    // can mutate *pi and can increment the pointer
    *pi = 1;
    pi++;
}

void f_02(int* const pi)
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    // can mutate *pi but cannot increment the pointer
    *pi = 1;
    //pi++;
}

void f_03(int const* pi) 
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    // cannot mutate *pi but can increment the pointer
    //*pi = 1;
    pi++;
}

void f_04(int const* const pi)
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    //*pi = 1;
    //pi++;
}

// These could affect the value of the pointer in the caller
void f_05(int*& pi) 
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    *pi = 1;
    pi++;       //this will mutate the pointer in the caller
}

void f_06(int const*& pi) 
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    // cannot mutate the value addressed by the pointer -> *pi = 1;
    pi++;  //this will mutate the pointer in the caller
}

void f_07(int* const& pi) 
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    *pi = 1;
    // cannot do pi++;
}

void f_08(int const* const& pi) 
{
    wprintf(L"%s\n", _CRT_WIDE(__FUNCDNAME__));

    // cannot do *pi = 1;
    // cannot do pi++;
}

// --------------------------------

void test_f()
{
    int const constInt = 9;
    int mutableInt = 10;
    int const* pConstInt{ &constInt };
    int* pMutableInt{ &mutableInt };

    f_01(pMutableInt);
    f_02(pMutableInt);
    f_03(pConstInt);
    f_04(pConstInt);

    f_07(pMutableInt);
    f_08(pConstInt);
    // do these last!
    f_05(pMutableInt);
    f_06(pConstInt);

    // output is:
    //? f_01@@YAXPAH@Z
    //? f_02@@YAXQAH@Z
    //? f_03@@YAXPBH@Z
    //? f_04@@YAXQBH@Z

    //? f_07@@YAXABQAH@Z
    //? f_08@@YAXABQBH@Z
    //? f_05@@YAXAAPAH@Z
    //? f_06@@YAXAAPBH@Z


更进一步,我们在此演示您可以将可变值传递给 const 方法。请注意对 const 指针的可变引用的异常 - 在这种情况下,参数无法转换:

    // here we demonstrate that you can pass the mutable int to the const int methods
    // - which is why the overload ambiguity would occur
    f_01(pMutableInt);
    f_02(pMutableInt);
    f_03(pMutableInt);
    f_04(pMutableInt);

    f_07(pMutableInt);
    f_08(pMutableInt);
    f_05(pMutableInt);
    //f_06(pMutableInt); - this is not allowed


那么这一切的意义何在?基本上只在没有歧义的情况下才使用重载。我个人避免使用它们。

【讨论】:

    猜你喜欢
    • 2016-11-16
    • 2022-01-19
    • 2019-10-18
    • 1970-01-01
    • 2011-01-08
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多