指针和常量的交互和重载

interaction of pointer and const and overload

如您所见,我对混合指针 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
*/

这里真正的问题在于理解关于类型的“right-to-left”规则。它因 const char 等价于 char const.

的“最左边 const 的特例”的存在而有些混乱

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

有许多文章解释了 right-to-left 规则,并且有一个 cdecl 工具用简单的英语描述了 C 类型。参见 How do you read C declarations? and https://www.codeproject.com/Articles/7042/How-to-interpret-complex-C-C-declarations

还有一个web-basedcdecl:https://cdecl.org/

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


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

有趣的是,在 Visual-C++ 编译器中,当 pass-by-value 参数为 const 时,名称重整 不同的。此示例查看使用 pointer-to-int、pointer-to-int-const、按值传递(const 和 not)、按引用传递(const 和 not)的 8 种组合。

请注意,如果所有方法都被调用 f_01,编译器会报错,因为它提供了多个可能的选项并且无法解决歧义,因为您可以将 mutable-value 传递给 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


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