首页 > 其他 > 详细

如何定义一个只能在堆/栈上生成对象的类?

时间:2021-03-27 08:59:17      阅读:25      评论:0      收藏:0      [点我收藏+]

原链接:https://blog.csdn.net/shanghx_123/article/details/86490937

如何定义一个只能在堆/栈上生成对象的类?

前言

C++中,建立对象可以分为两种,一种是在栈上,一种是在堆上。在栈上称为静态建立,而后者称为动态建立。
静态建立一个类的对象,是由编译器自动为对象在栈空间中分配内存,然后调用该对象的构函数形成一个对象,这种方法是直接调用类的构造函函数。
动态建立对象,则是通过new运算符在堆上建立对象。具体步骤是:先执行operator new()函数,在堆上先开辟一段空间;然后然后调用构造函数进行初始化。这种方法是间接调用构造函数。

1.只能建立在堆上

要定义一个只能在堆上生成对象的类,我们对比两种方法,都是要使用构造函数,所以如果将构造函数设置为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了。
说了一大堆,第二个问题还没有解决.

2.只能建立在栈上

如果只要在栈上建立对象,那么就是不能调用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

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!