三星 C++ 在堆-栈上建立对象

**如何限制类的对象只能在堆上创建?如何限制对象只能在栈上创建?
**说明:C++ 中的类的对象的建立分为两种:静态建立、动态建立。

静态建立: 由编译器为对象在栈空间上分配内存,直接调用类的构造函数创建对象。例如:A a;
动态建立: 使用 new 关键字在堆空间上创建对象,底层首先调用 operator new() 函数,在堆空间上寻找合适的内存并分配;然后,调用类的构造函数创建对象。例如:A *p = new A();

1、类的构造函数

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值

1
2
3
4
5
class A {
public:
A(); //无参数的构造函数
// A(int line); //有参数的构造函数
};

2、类的析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。 析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

1
2
3
4
5
class A {
public:
A(); // 构造函数声明
~A(); //析构函数声明
};

3、限制对象建立在堆上

在栈上创建的必要条件是,构造函数和析构函数必须有外部访问权限。

限制对象只能建立在堆上:

最直观的思想:避免直接调用类的构造函数,因为对象静态建立时,会调用类的构造函数创建对象。但是直接将类的构造函数设为私有并不可行,因为当构造函数设置为私有后,不能在类的外部调用构造函数来构造对象,只能用 new 来建立对象。但是由于 new 创建对象时,底层也会调用类的构造函数,将构造函数设置为私有后,那就无法在类的外部使用 new 创建对象了。因此,这种方法不可行。

解决方法 1:

将析构函数设置为私有。原因:静态对象建立在栈上,是由编译器分配和释放内存空间,编译器为对象分配内存空间时,会对类的非静态函数进行检查,即编译器会检查析构函数的访问性。当析构函数设为私有时,编译器创建的对象就无法通过访问析构函数来释放对象的内存空间,因此,编译器不会在栈上为对象分配内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A
{
public:
A() {}
void destory()
{
delete this;
}

private:
~A()
{
}
};

该方法存在的问题:

用 new 创建的对象,通常会使用 delete 释放该对象的内存空间,但此时类的外部无法调用析构函数(因为我们已经把析构函数设置为私有啦,所以我们需要自己写一个成员函数,来释放内存),因此类内必须定义一个 destory() 函数,用来释放 new 创建的对象。

无法解决继承问题, 因为如果这个类作为基类,析构函数要设置成 virtual,然后在派生类中重写该函数,来实现多态。但此时,析构函数是私有的,派生类中无法访问。

解决方法2:

构造函数设置为 protected,并提供一个** public 的静态函数来完成构造**,而不是在类的外部使用 new 构造;将析构函数设置为 protected。原因:类似于单例模式,也保证了在派生类中能够访问析构函数。通过调用 create() 函数在堆上创建对象。

private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A
{
protected:
A() {}
~A() {}

public:
static A *create()
{
return new A();
}
void destory()
{
delete this;
}
};

4、限制对象建立在堆上

由于我们利用new来分配堆空间,再调用类的构造函数构建对象。因此,我们重载 operate new()为私有,就可以限制对象建立在堆上。同时由于重载operate new()为私有,也得重载operate delete();

new和delete是C++中的表达式,用于创建一个新的对象。它们是对堆中的内存进行申请和释放,而且这两个都是不能被重载的要实现不同的内存分配行为,需要重载operator new和operator delete而不是new和delete。

1
2
3
4
5
6
7
8
9
class A
{
private:
void *operator new(size_t t) {} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void *ptr) {} // 重载了 new 就需要重载 delete
public:
A() {}
~A() {}
};

5、总结

1、对象建立的两种方式:

静态建立:直接调用类的构造函数创建对象。此时对象在栈上。

动态建立:使用new关键字,再调用类的构造函数,A *p = new A()。此时对象在堆上。

2、限制对象建立在栈上

1、设置析构函数为私有。缺点:①需要写一个销毁的成员函数用于释放内存,因为用于销毁对象的析构函数私有了,不能被外部调用。 ②无法继承

2、将构造函数和析构函数全部设为protected通过调用成员函数动态创建和删除(这个函数得自己写)。

**3、限制对象建立在堆上 **

重载 operator new()和operator delete()为私有