昨天吃饭和宽妹讨论起以前看过的如何创建一个不能被继承的类,具体实现见
http://blog.csdn.net/kuaile123/article/details/21321471
同学提到了第一种方法,说new过之后还需要delete释放内存,这样需要把析构函数也设置为private。
然后就想到能不能创建一个变量,返回此变量,而不是new一个,这样可以将析构函数设置为pubic不需要delete。宽妹说这种方法肯定行不通,因为这样创建是在栈上创建的,当返回时变量已经无效了。事实证明是可行的。
class A{
private:
int i;
A( int j=0 ){
i=j;
printf("construct: %d \n",i);
}
public:
static A get_A0(){A a(2);return a;}
};
A a0 = A::get_A0();//调用
后讨论说他说的是返回变量的地址是有错的,上述之所以成功是因为后面调用后使用了一个复制构造函数,这样会造成效率低。然后再次写了返回变量地址的方法,如下所示:
//class A 定义里面
public:
static A* get_A1(){A a(2); return &a; }
A* a1 = A::get_A1();//调用
显示创建是没用问题的,宽妹说调用此对象的一个成员函数肯定会出错,由此,我们添加了一个成员函数,如下所示:
//class A 定义里面
public :
int f(){
printf("%d \n",i);
return i;
}
//调用
a1->f();
输出为:
construct: 2
4062396
而new的调用是这样的:
static A* get_A2(){ return new A(3); }
A* a2 = A::get_A2();
a2->f();
输出为:
construct: 3
3
显示堆上创建的调用是没有错的,栈上创建的调用结果是错误的。因为栈上创建的是局部变量,用new是在堆上创建的,如果不手动释放会一直存在,而在栈上创建的出了作用域会失效,所以在堆上创建调用函数时没有问题的。
但是依然可以打印说明仍然可以进入函数,但是只是打印成员数据时出错,此时我们讨论了C++对象模型,宽妹认为成员函数是不放置在类对象里的,而是经过混淆放置于一个全局的位置,所以虽然类对象是错误的,但是依然可以调用成员函数。为此又写了一个调用方法来证实,将随便一个变量的地址赋值给类对象,照样能打印,数据还是此变量的值,如下所示:
int i =100;
A* a = reinterpret_cast<A*>(&i);
a->f();
输出为:
100
由此可见没有调用构造函数直接访问了i的地址。我们翻了《C++对象模型》这本书,讨论了出现这种结果的原因,C++对象模型中,只有非静态数据成员被放置在每一个类对象中,而其他的如静态数据成员、静态和非静态函数成员都是放置于类对象外,可以理解为一个类的空间中,每个对象只是共享同一份。这样也就解释了为什么类对象错误但是依然能够调用类成员的原因。当创建一个类变量返回该变量的地址时,复制该指针,而当退出此函数时随即此变量已经失效,a1指针实际上指向的是一块无效的栈空间,当调用函数f()时,只是从类中调用,不经过类对象a1,f()中需要对象的数据成员i,就到认为正确的地址空间去找,a1+数据成员在a1中的偏移,而此时此段可能已经被改写,此时不会返回正确的结果。同理,我们随便将一个变量的地址强制转换为类对象的指针,此时也是上述的流程,上述巧合是因为第一个声明的成员变量地址和类对象地址是一致的,而且对象中的i和所声明变量都是int型,读取时都会读取i的地址,当我们将变量设置为float等,此时就会有截断等,打印出的也不是变量的值。
有些问题在书上看到过,但是距离用理论来分析还是有一定距离,幸而周围有几个小伙伴,尤其是宽妹,让我对一些东西的理解更深一些。三个臭皮匠顶个诸葛亮,O(∩_∩)O哈哈~