禁止产生堆对象VS禁止产生栈对象

本文探讨了在C++中如何禁止创建特定类型的堆对象和栈对象。通过将new/delete操作符设为私有,可以阻止堆对象的生成;而通过将析构函数设为私有或受保护,则能限制栈对象的生成。

首先想一想什么时候会用到禁止产生堆对象呢?

      当我们需要能在异常的情况下自动释放封装的资源时,禁止创建对象就派上用场了,可以自己创建一个资源封装类,该类对象只能在栈中产生.
      产生堆对象的唯一方法是使用new操作,如果我们禁止使用new不就行了么。再进一步,new操作执行时会调用operator new,而operator new是可以重载的。方法有了,就是使new operator 为private,为了对称,最好将operator delete也重载为private。现在,你也许又有疑问了,难道创建栈对象不需要调用new吗?是的,不需要,因为创建栈对象不需要搜索内存,而是直 接调整堆栈指针,将对象压栈,而operator new的主要任务是搜索合适的堆内存,为堆对象分配空间,这在上面已经提到过了。好,让我们看看下面的示例代码:

#include <stdlib.h> //需要用到C式内存分配函数
class Resource ; //代表需要被封装的资源类
class NoHashObject
{
 private:
  Resource* ptr ;//指向被封装的资源
  ... ... //其它数据成员
  void* operator new(size_t size) //非严格实现,仅作示意之用
  {
   return malloc(size) ;
  }
  void operator delete(void* pp) //非严格实现,仅作示意之用
  {
   free(pp) ;
  }
 public:
  NoHashObject()
  {
   //此处可以获得需要封装的资源,并让ptr指针指向该资源
   ptr = new Resource() ;
  }
  ~NoHashObject()
  {
   delete ptr ; //释放封装的资源
  }
};
NoHashObject现在就是一个禁止堆对象的类了,如果你写下如下代码:

NoHashObject* fp = new NoHashObject() ; //编译期错误!
delete fp ;

  上面代码会产生编译期错误。好了,现在你已经知道了如何设计一个禁止堆对象的类了,你也许和我一样有这样的疑问,难道在类 NoHashObject的定义不能改变的情况下,就一定不能产生该类型的堆对象了吗?不,还是有办法的,我称之为“暴力破解法”。C++是如此地强大, 强大到你可以用它做你想做的任何事情。这里主要用到的是技巧是指针类型的强制转换。

void main(void)
{
 char* temp = new char[sizeof(NoHashObject)] ;

 //强制类型转换,现在ptr是一个指向NoHashObject对象的指针
 NoHashObject* obj_ptr = (NoHashObject*)temp ;

 temp = NULL ; //防止通过temp指针修改NoHashObject对象

 //再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员
 Resource* rp = (Resource*)obj_ptr ;

 //初始化obj_ptr指向的NoHashObject对象的ptr成员
 rp = new Resource() ;
 //现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
 ... ...

 delete rp ;//释放资源
 temp = (char*)obj_ptr ;
 obj_ptr = NULL ;//防止悬挂指针产生
 delete [] temp ;//释放NoHashObject对象所占的堆空间。
}

  上面的实现是麻烦的,而且这种实现方式几乎不会在实践中使用,但是我还是写出来路,因为理解它,对于我们理解C++内存对象是有好处的。对于上面的这么多强制类型转换,其最根本的是什么了?我们可以这样理解:

  某块内存中的数据是不变的,而类型就是我们戴上的眼镜,当我们戴上一种眼镜后,我们就会用对应的类型来解释内存中的数据,这样不同的解释就得到了不同的信息。

  所谓强制类型转换实际上就是换上另一副眼镜后再来看同样的那块内存数据。

  另外要提醒的是,不同的编译器对对象的成员数据的布局安排可能是不一样的,比如,大多数编译器将NoHashObject的ptr指针成员安排在对象空间的头4个字节,这样才会保证下面这条语句的转换动作像我们预期的那样执行:

Resource* rp = (Resource*)obj_ptr ;

  但是,并不一定所有的编译器都是如此。

  既然我们可以禁止产生某种类型的堆对象,那么可以设计一个类,使之不能产生栈对象吗?当然可以。

       那怎么禁止创建栈对象呢?
  前面已经提到了,创建栈对象时会移动栈顶指针以“挪出”适当大小的空间,然后在这个空间上直接调用对应的构造函数以形成一个栈对象,而 当函数返回时,会调用其析构函数释放这个对象,然后再调整栈顶指针收回那块栈内存。在这个过程中是不需要operator new/delete操作的,所以将operator new/delete设置为private不能达到目的。当然从上面的叙述中,你也许已经想到了:将构造函数或析构函数设为私有的,这样系统就不能调用构 造/析构函数了,当然就不能在栈中生成对象了。

  这样的确可以,而且我也打算采用这种方案。但是在此之前,有一点需要考虑清楚,那就是,如果我们将构造函数设置为私有,那么我们也就不能用 new来直接产生堆对象了,因为new在为对象分配空间后也会调用它的构造函数啊。所以,我打算只将析构函数设置为private。再进一步,将析构函数 设为private除了会限制栈对象生成外,还有其它影响吗?是的,这还会限制继承。

  如果一个类不打算作为基类,通常采用的方案就是将其析构函数声明为private。

  为了限制栈对象,却不限制继承,我们可以将析构函数声明为protected,这样就两全其美了。如下代码所示:

class NoStackObject
{
 protected:
  ~NoStackObject() { }
 public:
  void destroy()
  {
   delete this ;//调用保护析构函数
  }
};

  接着,可以像这样使用NoStackObject类:

NoStackObject* hash_ptr = new NoStackObject() ;
... ... //对hash_ptr指向的对象进行操作
hash_ptr->destroy() ;

  呵呵,是不是觉得有点怪怪的,我们用new创建一个对象,却不是用delete去删除它,而是要用destroy方法。很显然,用户是不习惯这 种怪异的使用方式的。所以,我决定将构造函数也设为private或protected。这又回到了上面曾试图避免的问题,即不用new,那么该用什么方 式来生成一个对象了?我们可以用间接的办法完成,即让这个类提供一个static成员函数专门用于产生该类型的堆对象。(设计模式中的singleton 模式就可以用这种方式实现。)让我们来看看:

class NoStackObject
{
 protected:
  NoStackObject() { }
  ~NoStackObject() { }
 public:
  static NoStackObject* creatInstance()
  {
   return new NoStackObject() ;//调用保护的构造函数
  }
  void destroy()
  {
   delete this ;//调用保护的析构函数
  }
};

  现在可以这样使用NoStackObject类了:

NoStackObject* hash_ptr = NoStackObject::creatInstance() ;
... ... //对hash_ptr指向的对象进行操作
hash_ptr->destroy() ;
hash_ptr = NULL ; //防止使用悬挂指针

  现在感觉是不是好多了,生成对象和释放对象的操作一致了。

 

From: http://hi.baidu.com/%D7%AA%C5%EE/blog/item/cda09418a97a7d0435fa41bd.html

<think>我们被要求讨论“要求或禁止产生对象”的方法和原理。在面向对象编程中,对象可以在(heap)上或(stack)上分配。通常,上分配的对象生命周期与作用域绑定,而上分配的对象需要手动管理(如C++)或由垃圾回收器管理(如Java)。有时,出于性能、安全或设计考虑,我们可能希望强制或禁止上创建对象。 参考引用: 引用[1]中提到构造器,但没有直接相关的内容。 引用[2]提到volatile关键字和指令重排序,与对象创建无关。 引用[3]是关于CPU部件的,不相关。 引用[4]是关于逻辑运算符,不相关。 引用[5]是关于monitor(锁)的,不直接相关。 因此,我们主要依靠自己的知识来回答。 我们将从以下几个方面讨论: 1. 什么是? 2. 为什么需要禁止上创建对象? 3. 如何禁止上创建对象?(以C++为例,因为Java中所有对象都在上) 4. 如何要求在上创建对象?(以C++和Java为例) 5. 总结 注意:由于问题没有指定语言,我们以C++和Java为例,因为这两种语言在内存管理上差异明显。 一、的区别 - :由编译器自动管理,对象生命周期与作用域相关。当离开作用域时,对象自动释放。分配速度快,但空间有限。 - :由程序员手动管理(C++)或垃圾回收器管理(Java)。对象生命周期可以超越作用域,直到显式释放(C++)或垃圾回收(Java)。分配速度相对慢,空间较大。 二、禁止上创建对象的理由 1. 性能:上分配更快,且没有释放开销。 2. 避免内存泄漏:对象需要手动释放,如果忘记释放会导致内存泄漏(C++)。 3. 实时性:上分配时间确定,而上分配时间不确定(可能触发垃圾回收,导致停顿)。 4. 安全性:对象不会产生悬挂指针,而对象如果释放后仍被引用,会导致未定义行为。 三、如何禁止上创建对象(以C++为例) 在C++中,如果我们想禁止上创建对象,只需要禁止使用`new`操作符。可以通过将`operator new`设为私有或删除来实现。 示例: ```cpp class HeapProhibited { private: // 将operator new声明为私有,并且不提供实现(或使用C++11的delete) static void* operator new(size_t size) = delete; static void* operator new[](size_t size) = delete; }; ``` 这样,任何尝试在上创建`HeapProhibited`对象的代码都会编译错误: ```cpp HeapProhibited obj; // 正确,在上 HeapProhibited* p = new HeapProhibited(); // 错误,因为operator new被删除 ``` 四、如何要求在上创建对象(即禁止上创建对象) 1. C++中:将析构函数设为私有(或受保护),并在类内部提供销毁对象的公有方法(如`release`)。因为对象在离开作用域时会自动调用析构函数,如果析构函数不可访问,则对象无法编译。 示例: ```cpp class StackProhibited { public: StackProhibited() {} void release() { delete this; } // 通过成员函数释放对象 private: ~StackProhibited() {} // 析构函数私有 }; // 使用 StackProhibited* p = new StackProhibited(); // 正确 p->release(); // 释放对象 // StackProhibited obj; // 错误:析构函数不可访问 ``` 2. Java中:所有对象都在上,上只能存储基本类型和对象引用。但是,我们可以通过将构造函数设为私有,然后提供静态工厂方法来创建对象,这样就不能在类外部使用`new`创建对象(从而不能直接创建对象?注意:Java中所有对象都在上,所以这里讨论的“禁止上创建对象”没有意义,因为上只能放引用。实际上,我们通常讨论的是如何控制对象的创建方式,比如强制使用工厂方法)。 五、总结 禁止或要求在上创建对象通常是为了控制对象的生命周期和分配方式,以满足性能、安全或设计约束。在C++中,我们可以通过控制`operator new`和析构函数的访问权限来实现;在Java中,由于对象都在上,我们主要关注如何控制对象的创建(如使用工厂方法)。 注意:在C++11之后,还可以使用`= delete`来禁用函数,这样更方便。 下面生成相关问题: </think>### 要求或禁止产生对象的方法与原理 在系统编程中,控制对象的内存分配位置()对性能、安全性和资源管理至关重要。以下是实现要求或禁止上分配对象的核心方法和原理,结合内存管理机制和语言特性: --- #### 一、禁止上分配对象 **适用场景**:需要保证对象生命周期严格绑定作用域、避免内存泄漏或满足实时性要求(如嵌入式系统)。 ##### 1. **C++实现方法** - **原理**:重载并禁用 `operator new` - 对象编译器自动管理,对象需显式调用 `new` - 删除 `operator new` 可阻断分配 - **代码示例**: ```cpp class HeapProhibited { private: // 禁用分配(C++11以上) static void* operator new(size_t) = delete; static void* operator new[](size_t) = delete; // 禁用释放 static void operator delete(void*) = delete; static void operator delete[](void*) = delete; public: HeapProhibited() {} // 构造器仅允许分配[^1] }; // 使用示例 HeapProhibited obj; // 合法:分配 // HeapProhibited* p = new HeapProhibited(); // 编译错误:operator new被删除 ``` ##### 2. **原理分析** - **内存布局**:对象编译器在函数帧预留空间,地址连续且自动回收(通过 `pop` 指令)。 - **性能优势**:避免分配的系统调用开销(如 `brk`/`mmap`)和碎片化问题。 - **安全性**:消除 `delete` 遗漏导致的内存泄漏风险。 --- #### 二、要求在上分配对象 **适用场景**:需要长生命周期对象、动态大小结构或跨作用域共享数据。 ##### 1. **C++实现方法** - **原理**:私有化析构函数 + 工厂模式 - 析构函数私有化使对象无法自动销毁 - 工厂方法强制通过 `new` 创建 - **代码示例**: ```cpp class HeapOnly { private: ~HeapOnly() {} // 私有析构函数阻止分配 public: static HeapOnly* create() { return new HeapOnly(); // 强制分配 } void destroy() { delete this; } // 显式释放 private: HeapOnly() {} // 私有构造器[^1] }; // 使用示例 HeapOnly* obj = HeapOnly::create(); // 合法:分配 obj->destroy(); // HeapOnly stackObj; // 编译错误:析构函数不可访问 ``` ##### 2. **Java实现方法** - **原理**:Java 中所有对象默认在分配 - 分配仅限基本类型和逃逸分析优化的局部对象 - 通过 `new` 关键字显式触发分配: ```java // 所有对象均在分配 Object heapObj = new Object(); ``` ##### 3. **原理分析** - **内存特性**: - 通过 **地址加法器** 计算逻辑地址(如 `段地址寄存器 << 4 + 偏移量`)[^3] - 动态扩展能力(对比的大小限制) - **多线程安全**:volatile 修饰指针可避免指令重排序(如 `public static volatile T t;`)[^2] - **垃圾回收**:由 GC 自动管理,避免显式释放(对比 C++)。 --- #### 三、关键机制对比 | **特性** | **分配** | **分配** | |------------------|-------------------------------|--------------------------------| | **生命周期** | 作用域绑定(自动回收) | 显式控制(需手动/GC回收) | | **速度** | 极快(编译时预留空间) | 较慢(系统调用+可能触发GC) | | **线程安全** | 线程独占(无竞争) | 需同步机制(如 monitor[^5]) | | **内存大小** | 固定且有限(可能溢出) | 动态扩展(仅受系统限制) | | **适用场景** | 短生命周期、小对象、实时系统 | 大对象、共享数据、跨作用域 | --- #### 四、设计注意事项 1. **C++析构函数安全**: - 虚析构函数对多态对象必要(避免资源泄漏)[^3] - 构造函数中禁止调用虚函数(未完成初始化)[^3] 2. **Java逃逸分析**: - JVM 可能优化局部对象分配(需关闭 `-XX:-DoEscapeAnalysis` 强制分配) 3. **内存屏障**: - 分配需考虑可见性(如 volatile 禁止指令重排序[^2]) 4. **资源管理**: - 对象需配套 RAII(C++)或 try-with-resources(Java) --- ### 总结 - **禁止分配**:通过禁用 `operator new`(C++)实现,保证对象作用域可控性。 - **要求分配**:通过私有化析构函数(C++)或依赖语言特性(Java)实现,支持动态生命周期。 - **核心权衡**:在性能()与灵活性()间取舍,结合线程安全(如monitor[^5])、内存模型(如段寄存器[^3])和系统约束设计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值