如何只允许在堆上创建类?

第一种思路,就是构造函数私有化,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once
class OnlyOnHeap
{
public:
~OnlyOnHeap() = default;
private:
OnlyOnHeap()=default;
};


int main()
{
OnlyOnHeap o1;
}

如果尝试在栈上构建此对象,则会报错,信息如下:

1
"OnlyOnHeap::OnlyOnHeap()" (已声明 所在行数:7,所属文件:"E:\UnrealWorld\Cpp\LearnMordenCpp\OnlyOnHeap.h") 不可访问

但是,此时如果想实现在堆上构造,new也用不了了。此时只能写一个显式的静态函数去调用私有的构造函数去创建类并返回类的指针,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
class OnlyOnHeap
{
public:
~OnlyOnHeap() = default;
static OnlyOnHeap* Create()
{
return new OnlyOnHeap();
}

private:
OnlyOnHeap()=default;
};

此时就可以通过create函数去间接在堆上构造这个类了。

此时发现一个问题:如果使用赋值,还是可以赋值到一个栈上的对象并且不报错:

1
2
OnlyOnHeap *o1=OnlyOnHeap::Create();
OnlyOnHeap o2 = *o1;//没有报错

此时还需要禁用拷贝构造和拷贝赋值函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#pragma once
class OnlyOnHeap
{
public:
~OnlyOnHeap() = default;
static OnlyOnHeap* Create()
{
return new OnlyOnHeap();
}
OnlyOnHeap(const OnlyOnHeap&) = delete;
OnlyOnHeap& operator=(const OnlyOnHeap&) = delete;

private:
OnlyOnHeap()=default;
};

换一种思路。如果要禁用在栈上构造对象,程序退出栈的时候会自动调用栈上类的析构函数。编译器会把析构函数插入到程序中,可以把析构函数设为private,是否可行?如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma once
class OnlyOnHeap
{
public:
OnlyOnHeap() = default;
static OnlyOnHeap* Create()
{
return new OnlyOnHeap();
}

OnlyOnHeap(const OnlyOnHeap&) = delete;
OnlyOnHeap& operator=(const OnlyOnHeap&) = delete;

private:
~OnlyOnHeap() = default;
};

int main()
{
OnlyOnHeap o1;//尝试在栈上创建对象
}

此时报错:

1
"OnlyOnHeap::~OnlyOnHeap() noexcept" (已声明 所在行数:18,所属文件:"E:\UnrealWorld\Cpp\LearnMordenCpp\OnlyOnHeap.h") 不可访问

所以,私有化析构函数也可以防止程序在栈上创建类。

最佳实践的写法如下,核心就是手写创建和销毁,间接去调用new和delete去管理堆上的内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once
class OnlyOnHeap
{
public:

static OnlyOnHeap* Create()
{
return new OnlyOnHeap();
}

void destory()
{
delete this;
}

OnlyOnHeap(const OnlyOnHeap&) = delete;
OnlyOnHeap& operator=(const OnlyOnHeap&) = delete;

private:
OnlyOnHeap() = default;
~OnlyOnHeap() = default;
};

如何只允许在栈上创建类?

这个就好写多了。平时在堆上创建类有两种方法,一种是用new,一种是malloc。

禁止使用new,就把new的运算符禁用即可,别忘了数组也要禁用。

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once

class OnlyOnStack
{
public:
OnlyOnStack()=default;
~OnlyOnStack() = default;

void* operator new(size_t) = delete;
void* operator new[](size_t) = delete;
void* operator new(size_t, void* p)=delete;
void* operator new[](size_t, void* p) = delete;

如果是malloc来创建对象。这里理解一下如何通过malloc分配的内存在堆上创建对象:

1
void* memory = malloc(sizeof(MyClass));  // 仅仅分配原始内存

malloc函数只分配一堆字节内存空间,并不会调用类的构造函数,返回的是一个void*指针。

如果我们用new操作的话,过程如下:

1
2
3
4
MyClass* obj = new MyClass();
// 1. void* memory = operator new(sizeof(MyClass)); // 分配内存
// 2. obj = static_cast<MyClass*>(memory);
// 3. obj->MyClass(); // 调用构造函数(编译器隐式调用)

一般如果显式地写malloc,后面是这么构造对象的:

1
2
3
4
5
6
// 其实也就是手动完成上面三步
void* memory = malloc(sizeof(MyClass)); // 1. 分配内存(替代operator new)
MyClass* obj = new(memory) MyClass(); // 2. 构造对象
// 使用对象...
obj->~MyClass(); // 3. 手动析构
free(memory); // 4. 释放内存

所以还要禁用掉placement new,也就是

1
void* operator new(size_t, void* p)=delete;

此时如果尝试在堆上创建类:

1
OnlyOnStack* os = new OnlyOnStack();

就会报错:

1
无法引用 函数 "OnlyOnStack::operator new(size_t)" (已声明 所在行数:9,所属文件:"E:\UnrealWorld\Cpp\LearnMordenCpp\OnlyOnStack.h") -- 它是已删除的函数