问题描述
您将看到,我对混合使用指针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
那么这一切的意义何在?本质上,仅在没有歧义的可能性时才使用重载。我个人避免使用它们。