指针与const的交互以及重载

问题描述

您将看到,我对混合使用指针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,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
*/

解决方法

这里真正起作用的是理解关于类型的“从右到左”规则。对于“ {left}的特殊情况”的存在有些困惑,其中const char等效于char const

在Nicolai Josuttis的书“ C ++模板”中,他倾向于使用“ const right”来避免使用特殊情况,因此使从右到左的规则保持一致。此视频的另一个倡导者是视频“东常量但西常量表达式”中的Dan Saks-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/

请注意,通过 value 和通过 reference 传递参数之间存在差异。当您按值传递指向方法的指针时,实际上是在给它一个副本,由该方法决定是否要使副本变异。通过引用传递参数时,您正在共享变量,因此const引用不能被该方法突变。


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

有趣的是,在Visual-C ++编译器中,当传递值参数为const时,名称处理 是不同的。此示例研究了使用以下两种组合的8种组合:使用指向int的指针,指向int的const指针,按值传递(常量和非常量),按引用传递(常量和非常量)。

请注意,如果所有方法都称为f_01,则编译器会抱怨,因为它带有多个可能的选项并且不能解决歧义,因为您可以将可变值传递给const值方法。


// 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


那么这一切的意义何在?本质上,仅在没有歧义的可能性时才使用重载。我个人避免使用它们。