构造函数和析构函数的一些问题

上一篇 / 下一篇  2012-06-25 13:18:04 / 个人分类:杂谈

1、构造函数和析构函数为什么没有返回值?

q'M#Op _fa,c0  构造函数和析构函数是两个非常特殊的函数:它们没有返回值.这与返回值为void的函数显然不同.后者虽然也不返回任何值,但还可以让它做点别 的事情,而构造函数和析构函数则不允许.在程序中创建和消除一个对象的行为非常特殊,就像出生和死亡,而且总是由编译器来调用这些函数以确保它们被执行. 如果它们有返回值,要么编译器必须知道如何处理返回值,要么就只能由客户程序员自己来显式的调用构造函数与析构函数,这样一来,安全性就被人破坏了。另 外,析构函数不带任何参数,因为析构不需任何选项.

E"u3Z O`J0

[3r yvuk B2l V0  如果允许构造函数有返回值,在某此情况下,会引起歧义。如下两个例子

?Y$T j8B _ta S g K-K4J0 51Testing软件测试网Z*Mu7k {^ UY

q4uTTX:B0
51Testing软件测试网 ],H/[ll8u-m

class C
}M {+~2|'P T#x0{
WHN!v(BJ0   public:
D(a F(Q8pRS Q0     C(): x_(0) {    }51Testing软件测试网 r']5f;_|:bh8w
     C(int i): x_(i) {   }

5B,l}&P&o{9P4b+F F0 51Testing软件测试网&ub)VK*{ ?-_8_

   private:51Testing软件测试网l/h&`q#Ki-lP
       int x_;51Testing软件测试网2QZ'S1hYk4DA"Z
};51Testing软件测试网$S/kdg2Af6Z%`

h}{5RRnq0  如果C的构造函数可以有返回值,比如int:51Testing软件测试网4bu_ oi

8S8O ?|*v;h0  int C():x_(0) { return 1; } //1表示构造成功,0表示失败51Testing软件测试网!|)lMF9A f \l N SG

Ve6s9fK"e*Q3Q0  那么下列代码会发生什么事呢?

5A9Z2oH E|^l0

h6R:B0hKu0  C c=C(); //此时c.x_==1!!!

N,T2J3]&a%z/ST0 51Testing软件测试网xKQWZ*ghA9D o

  很明显,C()调用了C的无参数构造函数。该构造函数返回int值1。恰好C有一个但参数构造函数C(int i)。于是,混乱来了。按照C++的规定,C c=C();是用默认构造函数创建一个临时对象,并用这个临时对象初始化c。此时,c.x_的值应该是0。但是,如果C::C()有返回值,并且返回了 1(为了表示成功),则C++会用1去初始化c,即调用但参数构造函数C::C(int i)。得到的c.x_便会是1。于是,语义产生了歧义。使得C++原本已经非常复杂的语法,进一步混乱不堪。

'nH]&n)cq c?*}n q'U0

Rn _*oc0  构造函数的调用之所以不设返回值,是因为构造函数的特殊性决定的。从基本语义角度来讲,构造函数返回的应当是所构造的对象。否则,我们将无法使用临时对象:

oa6^"y w;T0V@EC0

!|G^5V3Ji^ L4n"r0  void f(int a) {...}      //(1)

e~_;i6Yt$l H0 51Testing软件测试网 P-K'G^oG2T

  void f(const C& a) {...} //(2)51Testing软件测试网 y/U5i7Y }

f |Rb3y@j"LjS-K0  f(C()); //(3),究竟调用谁?51Testing软件测试网 i|!x!J}Zc5u

51Testing软件测试网oL9L"h-f s

  对于(3),我们希望调用的是(2),但如果C::C()有int类型的返回值,那么究竟是调(1)好呢,还是调用(2)好呢。于是,我们的重载体系,乃至整个的语法体系都会崩溃。

+_0sf)Ofr,E!M0 51Testing软件测试网)A)U;sU.wg1c

  这里的核心是表达式的类型。目前,表达式C()的类型是类C。但如果C::C()有返回类型R,那么表达式C()的类型应当是R,而不是C,于是便会引发上述的类型问题。

JQ0M/Q@A0

@v*]5S/t US'c1E0  2、显式调用构造函数和析构函数51Testing软件测试网7P4m'Zh^l$J'u

`~ HP#X;MD0 51Testing软件测试网f` }bC'H|9T a f

6e ft3MO^} va0#include <iostream>51Testing软件测试网G*m{w*]&a
using namespace std;51Testing软件测试网/T)[BnB.{!c
class MyClass51Testing软件测试网}7s Z9S#D7CT t
{
a@@Tt(A0  public:   
&M8I;zCcu6V,p0  MyClass()   
[W wUgV0  {        51Testing软件测试网{ @(ItuT.p
       cout << "Constructors" << endl;   
n|6EYfAJ0  }    51Testing软件测试网z@ Au3dP
  51Testing软件测试网Sh}/LV+f.H0Nt
  ~MyClass()    51Testing软件测试网2i%j;^j:X q bD)f
  {
D5Xj#ZZ`xV0       cout << "Destructors" << endl;51Testing软件测试网c4Y3V6JKxu
  }51Testing软件测试网 G)It5r&Mzb1|
};51Testing软件测试网9~5x S ci:W

q_6Z!NHfo4@0int main()51Testing软件测试网 XqE2HgG
{51Testing软件测试网;]8MW jw]-Iu]'D'O9p&f
   MyClass* pMyClass =  new MyClass;
D#X rx.UI#@0   pMyClass->~MyClass();
|/nT-y&wnv f&N"I3Q0   delete pMyClass;51Testing软件测试网B-Mr2}P @l
   return 0;
E4ncv%dW9K0}51Testing软件测试网RY/[G!iU"F(Pn

51Testing软件测试网9}M.umt\$I

  结果:

4`1TG+d5G7yUnT0

)K [X'd%oV0  Constructors51Testing软件测试网cQ$WnF
  Destructors        //这个是显示调用的析构函数51Testing软件测试网$FZ1@ K(D&h!BA
  Destructors        //这个是delete调用的析构函数51Testing软件测试网8]nJm3e+zs

51Testing软件测试网S&Uf"\,K0I|fe

  这有什么用?有时候,在对象的生命周期结束前,想先结束这个对象的时候就会派上用场了。

J3CQq']%AO0

G){.`H"P3c/\w'v0  由此想到的:

W5Tz7j B!Vxw]0 51Testing软件测试网G#CVv3O}

  new的时候,其实做了两件事,一是:调用malloc分配所需内存,二是:调用构造函数。51Testing软件测试网 {1x'ro^

H)J U l^S qY*k K h0  delete的时候,也是做了两件事,一是:调用析造函数,二是:调用free释放内存。51Testing软件测试网 h1V*H4bPE-y/j*D{

51Testing软件测试网PQ7^ i+\3`y

所以推测构造函数也是可以显式调用的。做了个实现。

;Qc fd.X[3C:LC0

U5V9o*YJiTe0

#j:k B A:U+D V)Q9tsv;P0
int main()
"Q Q\:AQ0N8v6f0{
$cvv Y#D!rW/n8\0MyClass* pMyClass = (MyClass*)malloc(sizeof(MyClass));
(b-zP&Y-o0pMyClass->MyClass();51Testing软件测试网#~0@2uO(d7F+y5{1Qw6M
// …
;@6cJg5p0}
51Testing软件测试网wGz.mlV PSOk)w?a

  编译pMyClass->MyClass()出错:

6Q:lHeN-dr0

1WnS5oq ['xP0  error C2273: 'function-style. cast' : illegal as right side of '->'operator51Testing软件测试网D u&i.meU

51Testing软件测试网{ } Hvw8Y2dy

  天啊,它以为MyClass是这个类型。

(\Z8pVK5x2]0

|6B@{~dEd0  解决办法有两个:

,] `u r5z9SH:o E0

ziK7C6}:po }0  第一:pMyClass->MyClass::MyClass();

ZDco_3ae xGw-K0

p1o$kQD5~c0  第二:new(pMyClass)MyClass();

d&JM%z(CcGA0 51Testing软件测试网2m}$KN%G%~

  第二种用法涉及C++ placement new 的用法 。

L$G)|E'i1l8Zr0 51Testing软件测试网+wG.C v9{o,]M

  placement new的作用就是:创建对象(调用该类的构造函数)但是不分配内存,而是在已有的内存块上面创建对象。用于需要反复创建并删除的对象上,可以降低分配释放内存的性能消耗。请查阅placement new相关资料。51Testing软件测试网x+D@@^a,X.CX9ar#a

51Testing软件测试网` B I3_$a q fL

  显示调用构造函数有什么用?51Testing软件测试网Y S|(@@/gh/]k

K1OR&jmC0  有时候,你可能由于效率考虑要用到malloc去给类对象分配内存,因为malloc是不调用构造函数的,所以这个时候会派上用场了。

;Dp8EM7GS5d/n0 51Testing软件测试网2~'Ea0N}6}tq0Y hO

  另外下面也是可以的,虽然内置类型没有构造函数。

h#PDT0P0

U0U.qM*y*hA0  int* i = (int*)malloc(sizeof(int));51Testing软件测试网S}0Q c"J
  new (i) int();51Testing软件测试网7Y&E)M |w2f2~J6{

8]i)q5T[1?o^0  3、关于拷贝构造函数为什么不能用值传递

]t5d G9U;LU1]|0 51Testing软件测试网)d/H|7J'x+Ta/D

  当你尝试着把拷贝构造函数写成值传递的时候,会发现编译都通不过,错误信息如下:

d6M c Eg3oy Q0 51Testing软件测试网$kz'\9}(nR0@`

  error: invalid constructor; you probably meant 'S (const S&)' (大致意思是:无效的构造函数,你应该写成。。。)51Testing软件测试网@5Ap(L L-L

51Testing软件测试网-@M`"n8F|0Y

  当编译错误的时候你就开始纠结了,为什么拷贝构造函数一定要使用引用传递呢,我上网查找了许多资料,大家的意思基本上都是说如果用值传递的话可能会产生死循环。编译器可能基于这样的原因不允许出现值传递的拷贝构造函数,也有可能是C++标准是这样规定的。

,y/Hy _sVTi-Z0 51Testing软件测试网 M5\^0uiQW'x4[

  如果真是产生死循环这个原因的话,应该是这样子的:

U,E,R8u)_R0

R#ydvd0

X!}6}$zJ.Pa0
51Testing软件测试网1x@7vOA:W K;D

#include<iostream>51Testing软件测试网"u1oHa`
using namespace std;
)d}WF@o a'l0class S
1`K6m4GP-a`%q0{51Testing软件测试网T4Q ~(i@4F
  int a;
9u%T B-_ S0  public:
`*n {F,A0  S(int x):a(x){}
6_nhg&_3V;F~en!?0  S(const S st){this->a=st.a;}  //拷贝构造函数
bm4Y-l2Ke0};51Testing软件测试网(I,]UY a s

_:os#]9~] ]0int main()51Testing软件测试网I kz,N X%DI8P
{51Testing软件测试网s]2}"Q'l}"qo
    S s1(2);
n\]jw7v0    S s2(s1);51Testing软件测试网9r7?"C vn Qh

h k I]8F;B*T(D'^0    return 0;
PFSGz T0}51Testing软件测试网._%R ^T3]/bv W

tp2u ?o,Md0  当给s2初始化的时候调用了s2的拷贝构造函数,由于是值传递,系统会给形参st重新申请一段空间,然后调用自身的拷贝构造函数把s1的数据成 员的值传给st。当调用自身的拷贝构造函数的时候又因为是值传递,所以。。。也就是说,只要调用拷贝构造函数,就会重新申请一段空间,只要重新申请一段空 间,就会调用拷贝构造函数,这样一直下去就形成了一个死循环。所以拷贝构造函数一定不能是值传递。51Testing软件测试网.h'm vx)G


TAG:

 

评分:0

我来说两句

Open Toolbar