静态分配: 由编译器为对象在栈空间中分配内存
动态分配: 使用new运算符为对象在堆空间中分配内存.
这个过程分为两步,第⼀步执行 operator new() 函数,在堆中搜索⼀块内存并进行分配;第⼆步调用类构造函数构造对象


1.1 追问: 类如何实现只能静态分配?

只有使用 new 操作符,才能够完成在堆上分配空间并初始化类对象,因此只要限制 new 操作符就可以实现对象仅能在栈上初始化;
所以可以将 new 操作符(new operator)以及 delete 操作符重载为 private:

  1. class A {
  2. public:
  3. A() { printf("constructor A\n"); }
  4. ~A() { printf("destructor A\n"); }
  5. private:
  6. void* operator new(size_t size) { return malloc(size); }
  7. void operator delete(void *ptr) {
  8. free(ptr);
  9. ptr = NULL;
  10. }
  11. };
  12. void TestStaticAlloc()
  13. {
  14. /*
  15. 动态分配的方式报错:
  16. error: 'static void* A::operator new(size_t)' is private within this context
  17. */
  18. // A* a = new A();
  19. A a;
  20. }

输出打印如下:

constructor A destructor A

结论:将 new operator 重载并声明为 private 即可限制类仅能静态分配。


1.2 追问:类如何实现只能动态分配?

即限制类对象不能直接调用构造函数初始化;
我们首先想到的是可以将构造函数声明为 private 属性,这样就无法在类外部调用构造函数来构造对象了;

构造函数声明为 private

此时类外部无法调用构造函数来构造对象,只能使用 new 操作符;举例如下:

  1. class DyTestClassFirst {
  2. private:
  3. DyTestClassFirst() { printf("constructor DyTestClassFirst\n"); }
  4. public:
  5. ~DyTestClassFirst() { printf("destructor DyTestClassFirst\n");
  6. };
  7. void TestDynamicAllocPre()
  8. {
  9. DyTestClassFirst* dy = new DyTestClassFirst();
  10. delete dy;
  11. }

上述代码输出结果如下: :::info error: ‘DyTestClassFirst::DyTestClassFirst()’ is private within this context ::: 结果解析:
我们知道 new operator() 初始化过程分为两步:调用 operator new() 分配一块未经初始化的内存,然后是调用构造函数,然而被声明为 private 的构造函数即无法被调用,所以该方案不可行

析构函数声明为 private

我们知道对象静态分配时,是由编译器在栈上分配空间,并调用构造函数初始化对象;当对象的生命周期结束,编译器会自动调用析构函数释放栈空间中的对象;假设使编译器无法调用类的析构函数,那么类的静态分配是否就无法执行了?
举例如下:

  1. class Parent {
  2. public:
  3. Parent() { printf("construct Parent\n"); }
  4. private:
  5. ~Parent() { printf("destruct Parent\n"); }
  6. };
  7. void TestDynamicAllocate()
  8. {
  9. Parent pa;
  10. }

上述代码编译输出如下: :::info error: ‘Parent::~Parent()’ is private within this context ::: 为什么编译器无法调用类的析构函数会导致静态分配无法通过编译?
因为编译器在为类分配栈对象的时候, 会先检查类的析构函数的可访问性。 如果析构函数在类外无法访问, 那么编译器会拒绝在栈空间为类对象分配内存。

为了在动态分配对象的时候能够回收对象的资源,我们需要显示定义一个 destroy()方法用于触发析构函数:

  1. class Parent {
  2. private:
  3. ~Parent() { printf("destruct Parent\n"); }
  4. public:
  5. Parent() { printf("construct Parent\n"); }
  6. void destroy() { delete this; }
  7. };
  8. void TestDynamicAllocate()
  9. {
  10. Parent *pa = new Parent();
  11. pa->destroy();
  12. }

上述代码输出如下: :::info construct Parent
destruct Parent :::

上述方法虽然限制了仅能动态分配,但是会导致该类无法被继承;
举例:

  1. class Parent {
  2. private:
  3. ~Parent() { printf("destruct Parent\n"); }
  4. public:
  5. Parent() { printf("construct Parent\n"); }
  6. void destroy() { delete this; }
  7. };
  8. class Child : public Parent {
  9. public:
  10. Child() : Parent() {}
  11. };

上述代码编译结果如下: :::info error: ‘Parent::~Parent()’ is private within this context ::: 析构函数被声明为了 private,则限制了类外部访问,从而使得子类亦无法访问,导致子类无法正常的去通过析构的方式释放其内部初始化得到的父类的对象。所以编译报错。

所以为了不限制继承,一般不会将析构函数声明为 private;

析构函数声明为 protected

可以将析构函数声明为 protected,类外部无法访问,但是可以由子类访问,即可解决上述问题:

  1. class Parent
  2. {
  3. public:
  4. Parent() { printf("construct Parent\n"); }
  5. void destroy() { delete this; }
  6. protected:
  7. virtual ~Parent() { printf("destruct Parent\n"); }
  8. };
  9. class Child : public Parent
  10. {
  11. public:
  12. Child() : Parent() { printf("construct Child\n"); }
  13. ~Child() { printf("destruct Child\n"); }
  14. };
  15. void TestDynamicAllocate()
  16. {
  17. //Parent pp; // ~Parent() 无法访问
  18. Parent* pa = new Child();
  19. pa->destroy();
  20. }

上述代码输出如下: :::info construct Parent
construct Child
destruct Child
destruct Parent :::

结论:将析构函数声明为 protected,然后通过子类动态创建对象,即能够限制类仅能动态分配。

参考:C++如何实现类对象只能动态分配或只能静态分配