MQL5의 객체 생성 및 파괴 순서

MetaQuotes | 1 7월, 2021

이 글은 무엇에 관한 것입니까?

MQL5 프로그램은 객체 지향 프로그래밍 (OOP) 개념으로 작성되었으며 사용자 정의 라이브러리를 만들 수 있는 새로운 가능성을 열어 줄 뿐만 아니라 다른 개발자의 완전하고 테스트된 클래스를 사용할 수 있도록 해줍니다. MetaTrader 5 클라이언트 터미널에 포함된 표준 라이브러리에는 수천 개의 메소드를 포함하는 수백 개의 클래스가 있습니다.

OOP를 최대한 활용하려면 개체를 만들고 삭제하는 방법에 대한 몇 가지 세부 정보를 MQL5 프로그램에서 명확히 해야 합니다. 객체 생성 및 삭제는 문서에 간략하게 설명되어 있으며 이 글에서는 이 주제를 예제로 설명합니다.

전역 변수의 초기화 및 초기화 해제

전역 변수의 초기화는 MQL5 프로그램 시작 직후와 함수 호출 직전에 수행됩니다. 초기화하는 동안 간단한 유형의 변수에 초기 값이 할당되고 개체에 선언된 경우 개체의 생성자가 호출됩니다.

예를 들어 두 개의 클래스 CObjectA와 CObjectB를 선언하겠습니다. 모든 클래스에는 간단한 Print 함수를 포함하는 생성자와 소멸자가 있습니다. 해당 클래스 유형의 변수를 전역으로 선언하고 스크립트를 실행 해 보겠습니다.

//+------------------------------------------------------------------+
//|                                         GlobalVar_TestScript.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
class CObjectA
  {
public:
                     CObjectA(){Print(__FUNCTION__," Constructor");}
                    ~CObjectA(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectB
  {
public:
                     CObjectB(){Print(__FUNCTION__," Constructor");}
                    ~CObjectB(){Print(__FUNCTION__," Destructor");}
  };
//--- declaring the objects globally
CObjectA first;
CObjectB second;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print(__FUNCTION__);
  }

스크립트 결과는 엑스퍼트 (Experts) 저널에 표시됩니다.

GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectA::ObjectA  Constructor
GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectB::ObjectB  Constructor
GlobalVar_TestScript (EURUSD,H1)    13:05:07    OnStart
GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectB::~ObjectB  Destructor
GlobalVar_TestScript (EURUSD,H1)    13:05:07    CObjectA::~ObjectA  Destructor

저널에서 초기화 순서가GlobalVar_TestScript.mq5 스크립트의 변수 선언 순서와 일치하고 MQL5 프로그램 롤아웃 전에 역순으로 초기화가 수행됨이 분명합니다.

로컬 변수의 초기화 및 초기화 해제

로컬 변수는 선언된 프로그램 블록의 끝에서 선언의 역순으로 초기화 해제됩니다. 프로그램 블록스위치 연산자, 루프 연산자 (for, whiledo-while), 함수 본문 또는 if-else 연산자의 일부일 수있는 복합 연산자입니다.

로컬 변수는 프로그램에서 사용되는 경우에만 초기화됩니다. 변수가 선언되었지만 선언된 코드 블록이 실행되지 않으면 이 변수는 생성되지 않으므로 초기화되지 않습니다.

이를 설명하기 위해CObjectA 및 CObjectB클래스로 돌아가서 새 클래스를 생성합니다.CObjectС. 클래스는 여전히 전역적으로 선언되지만 이러한 클래스의 변수는 이제 OnStart() 함수에서 로컬로 선언됩니다.

함수의 첫 번째 줄에 CObjectA 클래스변수를 명시적으로 선언해보겠습니다. 하지만 CObjectB 및 CObjectC 클래스의 개체는 별도의 블록으로 선언되며 입력 변수 실행 값에 따라 실행됩니다. MetaEditor에서 MQL5 프로그램의 입력 변수는 갈색으로 강조 표시됩니다.

//+------------------------------------------------------------------+
//|                                          LocalVar_TestScript.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property script_show_inputs
//--- input parameters
input bool     execute=false;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectA
  {
public:
                     CObjectA(){Print(__FUNCTION__," Constructor");}
                    ~CObjectA(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectB
  {
public:
                     CObjectB(){Print(__FUNCTION__," Constructor");}
                    ~CObjectB(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectC
  {
public:
                     CObjectC(){Print(__FUNCTION__," Constructor");}
                    ~CObjectC(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CObjectA objA;
//--- this block will NOT be executed if execute==false
   if(execute)
     {
      CObjectB objB;
     }
//--- this block WILL be executed if execute==false
   if(!execute)
     {
      CObjectC objC;
     }
  }
//+------------------------------------------------------------------+

결과는 다음과 같습니다.

LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectA::CObjectA  Constructor
LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectC::CObjectC  Constructor
LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectC::~CObjectC  Destructor
LocalVar_TestScript (GBPUSD,H1)    18:29:00    CObjectA::~CObjectA  Destructor

CObjectA 클래스개체실행 입력 매개 변수가 있는 값에 관계없이 항상 자동으로 먼저 초기화됩니다. 그러면 개체 objB 또는 개체 objC가 자동으로 초기화됩니다 - 입력 매개 변수 실행 값에 따라 실행되는 블록에 따라 다릅니다. 기본적으로 이 매개 변수는 거짓 값을 가지며 이 경우 objA 변수 초기화 후 objC 변수 초기화가 시작됩니다. 이것은 생성자와 소멸자 실행에서 명백합니다.

그러나 초기화 순서가 무엇이든 (실행 매개 변수에 관계없이) 복잡한 유형 변수의 초기화 취소는 초기화의 역순으로 수행됩니다. 이는 로컬 및 글로벌 클래스 객체 생성된 자동모두에 적용됩니다. 이 경우에는 차이가 없습니다.

동적으로 생성된 객체의 초기화 및 초기화 해제

MQL5에서 복합 개체는 자동으로 초기화되지만 개체 생성 프로세스를 수동으로 컨트롤하려면 개체 포인터를 사용해야 합니다. 일부 클래스의 개체 포인터로 선언된 변수는 개체 자체를 포함하지 않으며 해당 개체의 자동 초기화도 없습니다.

포인터는 로컬 및 / 또는 전역으로 선언 될 수 있으며 동시에 상속된 유형의 빈 값 NULL로 초기화 될 수 있습니다. 개체 생성은 새로운 연산자가 개체 포인터에 적용될 때만 수행되며 개체 포인터 선언에 의존하지 않습니다.

동적으로 생성된 객체는delete 연산자를 사용하여 삭제되므로 이는 반드시 처리해야 합니다. 예를 들어 CObjectA 유형 중 하나와 CObjectB 유형 중 하나와 객체 포인터가 있는 CObjectC 유형의 다른 변수 두 가지 변수를 전역적으로 선언해보겠습니다.

//+------------------------------------------------------------------+
//|                                       GlobalVar_TestScript_2.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
class CObjectA
  {
public:
                     CObjectA(){Print(__FUNCTION__," Constructor");}
                    ~CObjectA(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectB
  {
public:
                     CObjectB(){Print(__FUNCTION__," Constructor");}
                    ~CObjectB(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectC
  {
public:
                     CObjectC(){Print(__FUNCTION__," Constructor");}
                    ~CObjectC(){Print(__FUNCTION__," Destructor");}
  };
CObjectC *pObjectC;
CObjectA first;
CObjectB second;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   pObjectC=new CObjectC;
   Print(__FUNCTION__);
   delete(pObjectC);
  }
//+------------------------------------------------------------------+

사실에도 불구하고, 동적으로 생성된 객체 포인터 pObjectC는 정적 변수의 첫 번째두 번째 앞에 선언되지만, 바로 이 객체는 새로운 연산자에 의해 생성 될 때만 초기화됩니다. 이 예에서 new 연산자는 OnStart() 함수에 있습니다.

GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectA::CObjectA  Constructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectB::CObjectB  Constructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectC::CObjectC  Constructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    OnStart
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectC::~CObjectC  Destructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectB::~CObjectB  Destructor
GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    CObjectA::~CObjectA  Destructor

OnStart() 함수의 프로그램 실행이 연산자에 도달 할 때

   pObjectC=new CObjectC;

객체가 초기화되고 이 객체의 생성자가 호출됩니다. 그런 다음 프로그램은 이 문자열을 실행합니다

   Print(__FUNCTION__);

저널에 다음 텍스트를 출력합니다.

GlobalVar_TestScript_2 (EURUSD,H1)    15:03:21    OnStart

그런 다음 delete 연산자를 호출하여 동적으로 생성된 개체를 삭제합니다.

   delete(pObjectC);

따라서 개체는 new 연산자에 의해 생성되는 동안 동적으로 초기화되고 delete 연산자에 의해 삭제됩니다.

필수 요구 사항: object_pointer = new Class_Name 표현식을 사용하여 생성된 모든 개체는 항상 delete (object_pointer) 연산자를 사용하여 삭제해야 합니다. 어떤 이유로 동적으로 생성된 객체 (초기화된 블록 종료 후)가 delete 연산자를 사용하여 삭제되지 않은 경우 해당 메시지가 Experts 저널에 표시됩니다.


동적으로 생성된 개체 삭제

앞서 언급했듯이 동적으로 생성된 모든 개체는 new 연산자를 사용하여 초기화되며 항상 delete 연산자를 사용하여 삭제해야 합니다. 그러나 new 연산자는 객체를 생성하고 객체에 대한 포인터를 반환한다는 것을 잊지 마십시오. 생성된 개체 자체는 개체 포인터를 포함하는 변수에 없습니다. 여러 포인터를 선언하고 동일한 개체 포인터에 할당 할 수 있습니다.

//+------------------------------------------------------------------+
//|                                        LocalVar_TestScript_1.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|  simple class                                                    |
//+------------------------------------------------------------------+
class CItem
  {
public:
                     CItem(){Print(__FUNCTION__," Constructor");}
                    ~CItem(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declaring the first object pointer array 
   CItem* array1[5];
//--- declaring the first object pointer array 
   CItem* array2[5];
//--- filling arrays in the loop
   for(int i=0;i<5;i++)
     {
      //--- creating a pointer for the first array using new operator
      array1[i]=new CItem;
      //--- creating a pointer for the second array via copy from the first array
      array2[i]=array1[i];
     }
   // We "forgot" to delete objects before exiting the function. See "Experts" tab.
  }
//+------------------------------------------------------------------+

출력에는 삭제되지 않은 개체가 여러 개 남아 있다고 표시됩니다. 그러나 new 연산자가 5 개의 개체만 생성했기 때문에 10개가 아닌 5개의 삭제되지 않은 개체만 있을 것입니다.

(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    CItem::CItem  Constructor
(GBPUSD,H1)    12:14:04    5 undeleted objects left

동적으로 생성된 객체의 소멸자가 호출되지 않더라도 (객체가 delete 연산자를 사용하여 삭제되지 않음) 메모리는 여전히 지워집니다. 그러나 "Experts"저널에서는 개체가 삭제되지 않았다고 합니다. 이것은 부적절한 개체 관리를 찾아 내고 오류를 수정하는 데 도움이 될 수 있습니다.

다음 예에서는 두 포인터 배열 array1array2 각각에서 포인터를 삭제해 보겠습니다.

//+------------------------------------------------------------------+
//|                                        LocalVar_TestScript_2.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|  simple class                                                   |
//+------------------------------------------------------------------+
class CItem
  {
public:
                     CItem(){Print(__FUNCTION__," Constructor");}
                    ~CItem(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declaring the first object pointer array
   CItem* array1[5];
//--- declaring the second object pointer array
   CItem* array2[5];
//--- filling arrays in the loop
   for(int i=0;i<5;i++)
     {
      //--- creating a pointer for the first array using new operator
      array1[i]=new CItem;
      //--- creating a pointer for the second array via copy from the first array
      array2[i]=array1[i];
     }
//--- deleting object using pointers of second array
   for(int i=0;i<5;i++) delete(array2[i]);
//--- let's try to delete objects using pointers of first array
   for(int i=0;i<5;i++) delete(array2[i]);
// in Experts tab there are messages about trying to delete invalid pointer
  }
//+------------------------------------------------------------------+

전문가 탭의 결과는 이제 다릅니다.

(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::CItem  Constructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    CItem::~CItem  Destructor
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer
(GBPUSD,H1)    15:02:48    delete invalid pointer

CItem 생성 개체는 첫 번째 for() 루프에서 성공적으로 삭제되었지만 존재하지 않는 개체를 삭제하려는 추가 시도로 인해 두 번째 루프에서 잘못된 포인터에 대한 일부 메시지가 발생했습니다. 동적으로 생성된 개체는 한 번 삭제해야하며 개체 포인터를 사용하기 전에 CheckPointer() 함수로 확인해야 합니다.

CheckPointer() 함수를 사용한 포인터 검사

CheckPointer()는 포인터를 확인하는데 사용되며 포인터 유형을 식별할 수 있습니다. 동적으로 생성된 개체로 작업 할 때 가능한 두 가지가 있습니다.

  • 실행 블록 끝에서 삭제 취소
  • 이미 삭제된 개체 삭제 시도 

객체 상호 관계를 보여주는 또 다른 예를 들어 보겠습니다. 두 개의 클래스를 만들어 보겠습니다. 첫 번째 클래스 CItemArray에는 다른 클래스의 포인터 배열이 포함되며, 이는CItem입니다.

//+------------------------------------------------------------------+
//|                                        LocalVar_TestScript_3.mq5 |
//|                        Copyright 2009, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|  simple class                                                    |
//+------------------------------------------------------------------+
class CItem
  {
public:
                     CItem(){Print(__FUNCTION__," Constructor");}
                    ~CItem(){Print(__FUNCTION__," Destructor");}
  };
//+------------------------------------------------------------------+
//| class, containing pointer array of CItem class                   |
//+------------------------------------------------------------------+
class CItemArray
  {
private:
   CItem            *m_array[];
public:
                     CItemArray(){Print(__FUNCTION__," Constructor");}
                    ~CItemArray(){Print(__FUNCTION__," Destructor");Destroy();}
   void               SetArray(CItem &array[]);
protected:
   void               Destroy();
  };
//+------------------------------------------------------------------+
//|  filling pointers array                                          |
//+------------------------------------------------------------------+
CItemArray::SetArray(CItem &array[])
  {
   int size=ArraySize(array);
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++)m_array[i]=GetPointer(array[i]);
  }
//+------------------------------------------------------------------+
//|  releasing                                                       |
//+------------------------------------------------------------------+
CItemArray::Destroy(void)
  {
   for(int i=0;i<ArraySize(m_array);i++)
     {
      if(CheckPointer(m_array[i])!=POINTER_INVALID)
        {
         if(CheckPointer(m_array[i])==POINTER_DYNAMIC) delete(m_array[i]);
        }
      else Print("Invalid pointer to delete");
     }
  }

클래스 자체에는 오류가 없지만 사용하면 놀라움을 초래할 수 있습니다. 스크립트의 첫 번째 변형:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CItemArray items_array;
   CItem array[5];
   items_array.SetArray(array);
  }

이 스크립트 변형을 실행하면 다음 메시지가 표시됩니다.

(GBPUSD,H1)    16:06:17    CItemArray::CItemArray  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::CItem  Constructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItem::~CItem  Destructor
(GBPUSD,H1)    16:06:17    CItemArray::~CItemArray  Destructor
(GBPUSD,H1)    16:06:17    Invalid pointer to delete
(GBPUSD,H1)    16:06:17    Invalid pointer to delete
(GBPUSD,H1)    16:06:17    Invalid pointer to delete
(GBPUSD,H1)    16:06:17    Invalid pointer to delete

CItemArray 클래스 변수의 선언이 먼저 나오면 먼저 초기화되고 클래스 소멸자가 호출됩니다. 그런 다음 CItem 클래스 개체 포인터를 포함하는 array [5]가 선언됩니다. 그래서 각 개체의 초기화에 대한 5 개의 메시지가 표시됩니다.

이 간단한 스크립트의 마지막 줄에서 array[5] 배열의 포인터가 내부 개체 포인터 배열로 복사됩니다. items_array ( 'LocalVar_TestScript_4.mq5' 참조) .

   items_array.SetArray(array);

현재 스크립트 실행이 중지되고 자동으로 생성된 개체는 자동으로 삭제됩니다. 삭제할 첫 번째 개체는 마지막에 초기화된 개체입니다. 이것은 배열[5] 포인터 배열입니다. 5 개의 저널 레코드 에 대해 CItem 클래스소멸자호출확인합니다. 그런 다음 소멸자를 호출하는 메시지가 표시되는데, 이 소멸자는 items_array 개체의 소멸자이며, array [5] 변수 바로 전에 초기화되었기 때문입니다.

하지만 CArrayItem 클래스 소멸자는 보호된 Destroy() 함수를 호출하는데, 이는 CItem 객체를 삭제하려고합니다. m_array[]의 포인터를 통해 delete 연산자를 이용해서 말이죠. 포인터를 먼저 확인하고 잘못된 경우 개체가 삭제되지 않고 "삭제하기에 유효하지 않은 포인터 (Invalid pointer to delete)" 메시지가 표시됩니다.

저널에는 5 개의 레코드가 있습니다. 즉, m_array[] 배열의 모든 포인터가 잘못되었습니다. 이는 해당 포인터의 객체가 array[] 배열 초기화 해제를 하는 중에 이미 초기화 해제되었기 때문에 발생했습니다.

스크립트를 조정하여 items_arrayitems_array[] 변수의 선언을 바꿉니다.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CItem array[5];
   CItemArray items_array;
   items_array.SetArray(array);
  }

수정된 스크립트는 오류를 생성하지 않습니다. 먼저 items_array 변수가 마지막으로 선언되었으므로 초기화가 해제되었습니다. 초기화하는 동안 ~CItemArray() 클래스 소멸자가 호출되었고, 그 다음에는 Destroy() 함수라고 불렸습니다.

이 선언 순서에서 items_arrayarray[5] 배열 이전에 삭제됩니다. items_array 소멸자에서 호출되는 Destroy() 함수에는 포인터 객체가 여전히 존재하므로 오류가 발생하지 않습니다.

동적으로 생성된 개체의 올바른 삭제는 GetPointer() 함수의 예에서도 확인할 수 있습니다. 이 예에서 Destroy() 함수는 명시적으로 호출됩니다. 객체 삭제 적절한 순서를 보장하기 위해서 말이죠.

결론

보시다시피 개체 생성 및 삭제는 간단합니다. 이 글의 모든 예제를 검토하면 자동 및 동적으로 생성된 개체간에상호 관계 의 변형을 직접 만들 수 있습니다.

반드시 객체의 올바른 삭제 삭제 를 확인하고 소멸자를 올바르게 설계해야 잘못된 포인터에 액세스 할 때 오류가 발생하지 않습니다. new 연산자를 사용하여 동적으로 생성된 개체를 사용하는 경우 delete 연산자를 사용하여 이러한 개체를 올바르게 삭제해야 합니다.

이 글에서 MQL5에서 객체 생성 및 삭제 순서만 배웠습니다. 개체 포인터로 보안 작업을 구성하는 것은 이 문서의 범위를 벗어납니다.