How does c++ control how objects are created (or heap objects are prohibited from being created) and how many are created

  • 2020-11-03 22:32:52
  • OfStack

As we know, C++ divides memory into three logical areas: heap, stack, and static storage. In this case, I call the objects among them heap objects, stack objects, and static objects. In general, there is no limit to how many objects can be created on the heap or on the stack. But sometimes there are special needs.

1. Prohibit stack object creation

Stack object creation is prohibited, meaning objects can only be created on the heap. When a stack object is created, the top pointer is moved to "push out" the appropriate size, and the class's constructor is called directly on that space to form a stack object. At the end of the stack object's life cycle, the destructor is called to release the object, and then the top pointer is adjusted to reclaim that stack memory. No operator new/delete operation is required in this process, so setting operator new/delete to private will not do the trick.

You can make the constructor or destructor private so that the system can't call the constructor/destructor and, of course, can't generate objects on the stack. This is true, but one thing to note is that if we set the constructor private, we would not be able to use new to generate the heap object directly, because new would also call its constructor after allocating space to the object. So, if declaring both constructors and destructors as private is a big side effect, the best approach is to declare the destructor as private and the constructor as public.

Going one step further, does setting the destructor to private have any effect beyond limiting stack object generation? Yes, it also limits inheritance. If a class is not intended as a base class, the usual solution is to declare its destructor as private. To limit stack objects without limiting inheritance, we can declare the destructor as protected and have the best of both worlds. The following code is shown:


class NoStackObject{ 
protected: 
 ~NoStackObject(){} 
public: 
 void destroy(){ 
  delete this ;// Call the protection destructor  
 } 
};

The above classes create stack objects, such as NoStackObject obj; Time compilation will report an error, whereas with new the compilation will pass. One thing to note is that when you create a heap object with new, you need to call its destructor when you manually release the object's memory. This is assisted by a trick -- the pseudo-destructor destory is introduced, as shown in the code above.

Methods Expansion.

On closer inspection, we will find the above method awkward. We use new to create an object, but instead of using delete to delete it, we use the destroy method. Obviously, users won't get used to this weird way of using it. So you can set the constructor to private or protected as well. This brings me back to the question I tried to avoid above, which is, without new, how do I generate an object? We can do this indirectly by having the class provide an static member function dedicated to generating a heap object of that type. (The singleton pattern in design patterns can be implemented this way.) Let's take a look:


class NoStackObject { 
protected: 
 NoStackObject() { } 
 ~NoStackObject() { } 
public: 
 static NoStackObject* creatInstance() {
 return new NoStackObject() ;// Call the protected constructor  
} 
 void destroy() {
  delete this ;// Call the protected destructor  
 } 
};

You can now use the NoStackObject class as follows:


NoStackObject* hash_ptr = NoStackObject::creatInstance() ; 
... ... // right hash_ptr The object to which to operate  
hash_ptr->destroy() ; 
hash_ptr = NULL ; // Prevent the use of dangling Pointers 

Now that feels a lot better, operation 1 to generate and release objects is done.

2. Disallow heap object creation

We already know that the only way to generate a heap object is to use the new operation, if we disable the use of new. One step further, the new operation is called operator new, and operator new can be overloaded. The method is to make new operator private, and for symmetry, it is best to overload operator delete to private as well.


class NoStackObject{
private:
 static void* operator new(size_t size);
 static void operator delete(void* ptr);
};

// The user code 
NoStackObject obj0;    //OK
static NoStackObject obj1;  //OK
NoStackObject * pObj2 = new NoStackObject; //ERROR

If you also want to disable the array of heap objects, you can declare operator new[] and operator delete[] as private as well.

There is also a problem with inheritance. If the derived class overwrites operator new and operator delete and declares it as public, then the original private version of the base class will become invalid. Please refer to the following code:


class NoStackObject{
protected:
 static void* operator new(size_t size);
 static void operator delete(void* ptr);
};


class NoStackObjectSon:public NoStackObject{
public:
 static void* operator new(size_t size){ // Not strictly implemented, only used for signalling purposes  
  return malloc(size);
 };
 static void operator delete(void* ptr){ // Not strictly implemented, only used for signalling purposes  
  free(ptr);
 };
};

// The user code 
NoStackObjectSon* pObj2 = new NoStackObjectSon; //OK

3. Control the number of instantiated objects

In the game design, we use class CGameWorld as the abstract description of the game scene. However, during the game run, there is only one game scenario, that is, only one for the CGameWorld object. For instantiation of an object, there is one point that is 10 minutes certain: the constructor is called. So, if you want to control only one instantiation object of CGameWorld, the easiest way is to declare the constructor as private and provide one static object. As follows:


class CGameWorld
{
public:
 bool Init();
 void Run();
private:
 CGameWorld();
 CGameWorld(const CGameWorld& rhs);

 friend CGameWorld& GetSingleGameWorld();
};

CGameWorld& GetSingleGameWorld()
{
 static CGameWorld s_game_world;
 return s_game_world;
}

There are three main points in this design:

(1) The constructor of the class is private, which prevents the establishment of the object;
(2)GetSingleGameWorld is declared as a friend to avoid the restriction caused by private constructor;
(3)s_game_world is a static object, and the object is only 1.

When using the only instantiated object of CGameWorld, you can do the following:


GetSingleGameWorld().Init();
GetSingleGameWorld().Run();

If someone is uncomfortable with GetSingleGameWorld being a global function, or doesn't want to use friends, declaring CGameWorld as a static function of class CGameWorld will do the trick, as follows:


class CGameWorld
{
public:
 bool Init();
 void Run();
 static CGameWorld& GetSingleGameWorld();
private : 
 CGameWorld();
 CGameWorld(const CGameWorld& rhs);
};

This is the famous singleton pattern in design patterns: a class is guaranteed to have only one instance, and a global access point is provided to access it.

If we want the number of objects produced not to be one, but to be at most N(N) > 0). You can set a static count variable inside a class that is incremented by 1 when the constructor is called and subtracted by 1 when the destructor is called. As follows:


class CObject
{
public:
 CObject();
 ~CObject();
private:
 static size_t m_nObjCount;
 ...
};

CObject::CObject()
{
 if (m_nObjCount > N)
  throw;
 m_nObjCount++;
}

CObject::~CObject()
{
 m_nObjCount--;
}
size_t CObject::m_nObjCount;

Grasp the method that controls the number of instantiated objects in a class. When only 1 object is instantiated, the singleton pattern in the design pattern is adopted. When the instantiation object is N(N) > When 0), setting the counting variable is one idea.

Reading the sample code above also needs to note that there are no objects when an exception is thrown, that is, no objects after throw, which has two meanings:

(1) If throw ; Appears in an catch block or in a function called by an catch block to indicate that an exception has been rethrown. throw ; The expression will rethrow the exception that is currently being processed. We recommend this form because it preserves the polymorphic type information of the original exception. The exception object that is rethrown is the original exception object, not a copy.

(2) If throw ; Appears in a non-ES141en block to throw an exception that cannot be caught, even if catch(...) It cannot be caught up.

4. Summary

Heap objects, stack objects, and static objects are all referred to as memory objects. For a deeper understanding of memory objects, I recommend exploring C++ Object Model in Depth.

The above is c++ how to control the creation of objects (prohibit the creation of stack objects or heap objects) and the number of objects created detailed content, more about c++ control object creation and the number of information please pay attention to other related articles on this site!


Related articles: