(转)Const 重载解析

书上和网上在很多地方都对const 的重载做了一些解释,但感觉都不是很详细。还有很多同学在不同的地方发问关于const 重载的问题,这里我又重新看了一下,做了一个简单的分析也可能有不对的地方,欢迎讨论。
所谓重载,是指允许存在多个同名函数,而这些函数的参数表不同,即函数名相同但函数的签名不同。重载并不是面向对象编程的特有属性,这是因为重载是在编译阶段实现的,编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(这一点稍后有例子)。了

Const 参数重载解析:

关于const 重载几乎在所有c++的书中者提到过但大部分只是一句话,例如在《C++ primer》一书中这样描述:“可基于函数的引用形参是指向 const 对象还是指向非 const 对象,实现函数重载。将引用形参定义为 const 来重载函数是合法的,因为编译器可以根据实参是否为 const 确定调用哪一个函数。”
但是这一段描述并没有给出引用、指针和值传递前加const的实质区别是什么。在用非const的指针,引用和值均可转化为const的。这一点没有太多可说明的东东。

对于函数值传递的情况,因为参数传递是通过复制实参创建一个临时变量传递进函数的,函数内只能改变临时变量,但无法改变实参。则这个时候无论加不加const对实参不会产生任何影响。但是在引用或指针传递函数调用中,因为传进去的是一个引用或指针,这样函数内部可以改变引用或指针所指向的变量,这时const 才是实实在在地保护了实参所指向的变量。因为在编译阶段编译器对调用函数的选择是根据实参进行的,所以,只有引用传递和指针传递可以用是否加const来重载。
下面给出一个例子可能就更明白了:

   1: #include<iostream>
   2:  
   3:  class A{
   4:  public:
   5:     A();
   6:     int foo(int *test);
   7:     int foo(const int *test);
   8: };
   9: A::A(){
  10: }
  11:  int A::foo(int *test){
  12:     std::cout << *test << " A::foo(int *test)" <<std::endl;
  13:     return 1;
  14: }
  15:  int A::foo(const int *test){
  16:     std::cout << *test << " A::foo(const int *test)" <<std::endl;
  17:     return 1;
  18: }
  19:  int main()
  20: {
  21:     const int b =5;
  22:     int c = 3;
  23:     A a;
  24:     a.foo(&b);
  25:     a.foo(&c);
  26:     return 1;
  27: }

 

输出:

5 A::foo(const int *test)
3 A::foo(int *test)

那么编译器又是怎样工作的,通过g++ -S选项将汇编代码生成出来,通过AT&T汇编代码可以看出一些端倪来(之所以用AT&T汇编是因为VS生成的中间代码实在是让人头晕):

98 .globl _ZN1A3fooEPKi
99     .type    _ZN1A3fooEPKi, @function
100  _ZN1A3fooEPKi:
101 .LFB1402:
102     pushl    %ebp
103 .LCFI13:
104     movl    %esp, %ebp
105 .LCFI14:
106     subl    $8, %esp
107 .LCFI15:
108     movl    12(%ebp), %eax
109     movl    (%eax), %eax
110     movl    %eax, 4(%esp)
111     movl    $_ZSt4cout, (%esp)
112     call    _ZNSolsEi
113     movl    $.LC0, 4(%esp)
114     movl    %eax, (%esp)
115     call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
116     movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
117     movl    %eax, (%esp)
118     call    _ZNSolsEPFRSoS_E
119     movl    $1, %eax
120     leave
121     ret


122 .LFE1402:
123     .size    _ZN1A3fooEPKi, .-_ZN1A3fooEPKi
124     .section    .rodata
125 .LC1:
126     .string    " A::foo(int *test)"
127     .text
128     .align 2
129 .globl _ZN1A3fooEPi
130     .type    _ZN1A3fooEPi, @function
131  _ZN1A3fooEPi:
132 .LFB1401:
133     pushl    %ebp
134 .LCFI16:
135     movl    %esp, %ebp
136 .LCFI17:
137     subl    $8, %esp
138 .LCFI18:
139     movl    12(%ebp), %eax
140     movl    (%eax), %eax
141     movl    %eax, 4(%esp)
142     movl    $_ZSt4cout, (%esp)
143     call    _ZNSolsEi
144     movl    $.LC1, 4(%esp)
145     movl    %eax, (%esp)
146     call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
147     movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
148     movl    %eax, (%esp)
149     call    _ZNSolsEPFRSoS_E
150     movl    $1, %eax
151     leave
152     ret

 

如上面的代码函数:

int foo(int *test);和int foo(const int *test);分别被编译器生成名为:_ZN1A3fooEPKi和_ZN1A3fooEPi(这两个名字会因为编译器的不同而不同,名字只是一个区分的符号而已不用深究,只用知道重载的函数经过编译器的处理函数名字已经发生了变化。所以对于后面的汇编和链接工作就不存在重载的问题了。)这里也同时说明对重载来说在编译阶段已经完成。

对于a.foo(&b);因为变量b有const修饰所以就调用了int foo(const int *test);对于a.foo(&c);调用int foo(int *test);因为这个是精确匹配的。但是如果没有定义int foo(const int *test);则在代码24行会出现编译错误。反过来如果没有定义函数:int foo(int *test);如下:

   1: #include<iostream>
   2:  
   3:   class A{
   4:   public:
   5:      A();
   6:   //    int foo(int *test);
   7:       int foo(const int *test);
   8:  };
   9:  A::A(){
  10:  }
  11:   /*int A::foo(int *test){
  12:      std::cout << *test << " A::foo(int *test)" <<std::endl;
  13:      return 1;
  14:  }
  15:  */
  16:  int A::foo(const int *test){
  17:      std::cout << *test << " A::foo(const int *test)" <<std::endl;
  18:      return 1;
  19:  }
  20:  int main()
  21:  {
  22:      const int b =5;
  23:      int c = 3;
  24:      A a;
  25:      a.foo(&b);
  26:      a.foo(&c);
  27:      return 1;
  28:  }

 

则输出结果为:

1 5 A::foo(const int *test)
2 3 A::foo(const int *test)

原因c++ primer上讲的很清楚:“We can use a nonconst object to initializer either a const or nonconst reference. However, initializing a const reference to a nonconst object requires a conversion, whereas initializing a nonconst parameter is an exact match.”

const 成员函数重载的解析:

const 成员函数重载的解析和const参数重载解析的原理可以说是一样的。之所以这样说是因为const成员函数的解析可被看做是对函数this参数用const来修饰的过程。例如下面代码:

   1: #include<iostream>
   2:  class A{
   3:  public:
   4:      A();
   5:      int foo(int *test); //可看做:int foo(A *this,int *test);
   6:      int foo(int *test) const;//可看做:int foo(const A *this,int *test);
   7:  };
   8:  A::A(){
   9:  }
  10:  int A::foo(int *test){
  11:      std::cout << *test << "foo" <<std::endl;
  12:      return 1;
  13:  }
  14:  int A::foo(int *test) const {
  15:      std::cout << *test << "foo const" <<std::endl;
  16:      return 1;
  17:  }
  18:  int main()
  19:  {
  20:      int b = 5;
  21:      const A a;
  22:      a.foo(&b);
  23:      return 1;
  24:  }

输出:5fooconst

生成汇编为(部分):

98 .globl _ZNK1A3fooEPi
99 .type _ZNK1A3fooEPi, @function
100 _ZNK1A3fooEPi:
101 .LFB1402:
102 pushl %ebp
103 .LCFI13:
104 movl %esp, %ebp
105 .LCFI14:
106 subl $8, %esp
107 .LCFI15:
108 movl 12(%ebp), %eax
109 movl (%eax), %eax
110 movl %eax, 4(%esp)
111 movl $_ZSt4cout, (%esp)
112 call _ZNSolsEi
113 movl $.LC0, 4(%esp)
114 movl %eax, (%esp)
115 call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
116 movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
117 movl %eax, (%esp)
118 call _ZNSolsEPFRSoS_E
119 movl $1, %eax
120 leave
121 ret

165 .type _ZN1A3fooEPi, @function
166 _ZN1A3fooEPi:
167 .LFB1401:
168 pushl %ebp
169 .LCFI22:
170 movl %esp, %ebp
171 .LCFI23:
172 subl $8, %esp
173 .LCFI24:
174 movl 12(%ebp), %eax
175 movl (%eax), %eax
176 movl %eax, 4(%esp)
177 movl $_ZSt4cout, (%esp)
178 call _ZNSolsEi
179 movl $.LC1, 4(%esp)
180 movl %eax, (%esp)
181 call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
182 movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
183 movl %eax, (%esp)
184 call _ZNSolsEPFRSoS_E
185 movl $1, %eax
186 leave
187 ret

上面可以看到编译阶段的调用也是通过对重载函数的别名来实现的。

总结:

1.const重载主要是通过能否对传入的参数进行修改为判断的。

2.const参数重载和const函数重载机制都是一样的,因为对于const 函数重载可看做是对隐含的指针this的参数重载。

3.重载是在编译阶段已经完成,对于汇编和链接来说透明的。

转自(http://www.cnblogs.com/qingquan/archive/2010/10/21/1857444.html)

posted @ 2012-05-15 11:39  ForFreeDom  阅读(332)  评论(0编辑  收藏  举报