指针和常量的交互和重载
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/
还请注意,通过 value 和 reference 传递参数之间存在差异。当你按值传递一个指向方法的指针时,你实际上是在给它一个副本,并且由方法来声明它是否会改变副本。当您通过引用传递参数时,您 共享 变量,因此该方法不能改变 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
那么这一切的意义何在?基本上只有在没有歧义的情况下才使用重载。我个人避免使用它们。
如您所见,我对混合指针 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
.
在 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/
还请注意,通过 value 和 reference 传递参数之间存在差异。当你按值传递一个指向方法的指针时,你实际上是在给它一个副本,并且由方法来声明它是否会改变副本。当您通过引用传递参数时,您 共享 变量,因此该方法不能改变 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
那么这一切的意义何在?基本上只有在没有歧义的情况下才使用重载。我个人避免使用它们。