原链接:https://blog.csdn.net/shanghx_123/article/details/86490937
C++中,建立对象可以分为两种,一种是在栈上,一种是在堆上。在栈上称为静态建立,而后者称为动态建立。
静态建立一个类的对象,是由编译器自动为对象在栈空间中分配内存,然后调用该对象的构函数形成一个对象,这种方法是直接调用类的构造函函数。
动态建立对象,则是通过new运算符在堆上建立对象。具体步骤是:先执行operator new()函数,在堆上先开辟一段空间;然后然后调用构造函数进行初始化。这种方法是间接调用构造函数。
要定义一个只能在堆上生成对象的类,我们对比两种方法,都是要使用构造函数,所以如果将构造函数设置为private,很显然这种方法既不能在栈上建立对象,也不能在堆上。
当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。所以我们试着想如果把析构函数设为private能否解决这个问题,
#include <iostream>
using namespace std;
class person {
private:
~person(){}
};
int main()
{
// person p; // error -> ~person(){} 已声明,不可访问
person* ptr = new person;
system("pause");
return 0;
}
我们可以看到,person p;这句话是编译是不过的,他会说~person()不可访问。而通过new是可以的。原因就在于:类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。但是,虽然可以通过new来定义对象,可他是动态建立,所以释放也就有动态释放掉它的空间。所以必须写一个函数来动态释放掉new出的对象。
但是为什么不能直接在类外面用delete来释放new出来的空间?
这就要明白,new和delete底层是怎么做得。
new:new定义一个类的对象,先调用malloc函数在堆上为该对象开辟合适的空间,然后调用构造函数进初始化。
delete:当调用delete后,首先会调用该对象的析构函数,然后在调用free释放掉该段空间。
所以这时候就会明白,为什么不能直接delete刚才的对象,因为析构函数是私有的,类外面就不能访问,所以我们必须自己写一个函数来释放掉该空间。
#include <iostream>
using namespace std;
class person {
private:
~person(){}
};
int main()
{
// person p; // error -> ~person(){} 已声明,不可访问
person* ptr = new person;
/ delete ptr; // error -> ~person(){} 已声明,不可访问
system("pause");
return 0;
}
具体做法如下:
#include<iostream>
using namespace std;
class person
{
public:
person()
{}
void destroy()
{
delete this;//首先会调用析构函数,在调用free释放空间
}
private:
~person()
{}
};
int main()
{
//person p;不可以调用析构函数
person *ptr = new person;
//delete ptr;不可调用析构函数
ptr->destroy();
system("pause");
return 0;
}
这样做,还有问题,无法解决继承的问题,不能解决继承问题,那么多态就实现不了,从而父类的析构函数就不能定义为虚函数。我们先来看下他为什么?
问题就是:如果person作为其他类的父类,当子类继承它时,其子类是定义不出对象的。为什么???
先慢慢理一下,当父类的析构是private时,如果子类继承父类,这时候虽然看起来没什么问题,但是只要你一定义子类的对象(在栈/堆上定义),就会出错。
class student : public person
{
public:
/*student() {}
~student() {}*/
};
int main()
{
student s; // 无法引用"student"的默认构造函数 -- 它是已删除的函数
student* sptr = new student; //
system("pause");
return 0;
}
原因在于:
1.首先父类的析构函数是private,那么对于子类,其父类的析构函数是不可见的,也就是说,子类根本就不能访问父类的析构函数。
2.子类继承父类,那么父类就是子类的一部分,当子类的对象释放时,先调用子类的析构,再就会调用父类的析构函数。而子类如果这时假如能定义出对象,那么在它调用父类析构函数时,根本就拿不到,所以子类的对象就释放不了。
3.如果是这样的话,子类的对象能定义出来,但释放不了,那么编译器干脆直接就不让它定义出对象,(虽然父类的构造函数时public的,但其实子类的构造函数直接被删除了。在VS2017环境下),这样就避免了定义出对象,释放不了而导致内存泄漏。
但我们的问题确没有解决,我们现在的问题就是让他能够解决继承的问题,从而实现多态。
这时候,protected这个继承机制好像就派上用场了。我们把析构函数设置为protected,问题能不能解决????
class person {
private:
protected:
~person() {}
};
class student : public person
{
public:
student() {}
~student() {}
};
int main()
{
student s;
student* sptr = new student;
system("pause");
return 0;
}
试了一下,看样子可以编过,那它是怎么做的呢???
1.如果父类的析构函数是protected,此时子类就可以访问到父类的析构函数,只不过只能在子类里面访问而已。
2.当定义一个子类的对象时(在栈上),首先调用父类构造函数 ,再调用子类的构造函数,而这两个构造函数都是可见的。
3.当要释放掉对象时,先是调用子类的析构函数(public可见),在调用父类的析构函数(protected,对于子类也是可见),这样,子类的对象就可以完全被释放,因此对象也就可以定义出来。
4.如果在堆上定义子类对象(通过new),只不过就是在构造前动态开辟空间,在析构后,动态释放空间。
这样看似就解决了继承的问题,如果要实现多态,那么将父子类对应的函数声明为virtual,就构成了多态。这样父类的析构函数也可以声明为虚函数,这样一来,当父类的指针指向子类的对象时,用delete就会直接释放掉子类的对象,因为此时父子类的析构函数已经构成重写。
代码如下:
#include<iostream>
using namespace std;
class person
{
public:
person()
{}
void destroy()
{
delete this;//首先会调用析构函数,在调用free释放空间
}
virtual void func()
{
cout << "test person" << endl;
}
protected:
virtual ~person()
{
cout << "~person()" << endl;
}
};
class student :public person
{
public:
student()
{}
virtual ~student()
{
cout << "~student()" << endl;
}
virtual void func()
{
cout << "test student" << endl;
}
};
void test(person* p)
{
p->func();
}
int main()
{
person* ptr = new person;
student* sptr = new student;
test(ptr);//调用父类的func
test(sptr);//调用子类的func
cout << endl << "释放父类对象:" << endl;
ptr->destroy();
cout << endl << "释放父类对象:" << endl;
sptr->destroy();
cout << endl << "父类指针指向子类对象:" << endl;
person* p = new student;
cout << endl << "释放掉子类对象" << endl;
p->destroy();//作用就等于delete p;但必须通过此函数才能释放
system("pause");
return 0;
}
可以看到,
1.父子类的函数可以构成重写,从而实现多态。
2.父子类的对象可以正常释放,只不过父类的对象需要调用特定的destroy函数来释放对象。
3.当父类指针指向之类对象时,可以动态的释放掉子类的对象,而不是父类的对象。
这样看起来,使用new建立对象,却使用destory函数释放对象,而不是使用delete。使用还有点不太规范,我们可以把父类的构造函数和析构都声明为protected,这样在父类外面就都通过一个函数接口创建和释放对象。看起来比较舒服。
具体做法就是:
可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。
代码如下:
class person
{
public:
static person* create()
{
return new person();
}
void destroy()
{
delete this;//首先会调用析构函数,在调用free释放空间
}
virtual void func()
{
cout << "test person" << endl;
}
protected:
person()
{
cout << "person()" << endl;
}
virtual ~person()
{
cout << "~person()" << endl;
}
};
int main()
{
person* ptr = person::create();
ptr->destroy();
return 0;
}
这样就ok了。
说了一大堆,第二个问题还没有解决.
如果只要在栈上建立对象,那么就是不能调用new和delete这两个函数,C++是支持重载new和delete,所以只需将new和delete重载为类的私有成员里面,就ok了。
class person
{
public:
person()
{
cout << "person()" << endl;
}
~person()
{
cout << "~person()" << endl;
}
private:
void* operator new(size_t size)
{
return malloc(size);
}
void operator delete(void* p)
{
free(p);
}
};
原文:https://www.cnblogs.com/jemmyzhong/p/14584743.html