OOP, mql5의 템플릿 및 매크로, 미묘함 및 사용 기술

 
" mql5 언어의 특성, 미묘함 및 작업 방법 "과 관련이 없는 댓글이 이 항목으로 이동되었습니다.
 
fxsaber :

이것은 MQL5의 표준 동작입니다. 정적 변수는 전역 변수 다음에 작동하기 시작합니다.

당신은 이것에 대해 매우 진지해질 수 있습니다.

이 "표준 행동"에 대한 주제를 계속합시다. 아무도 보편적 인 해결책을 제시하지 못했습니다. 어떻게 처리합니까? 지금까지는 모든 정적 변수 를 전역 변수로 교체하는 것만 보았습니다(MQ는 모든 것을 전역 수준으로 직접 가져오게 함). 그러나 이것은 템플릿과 매크로에서 수행할 수 없습니다.

정적 템플릿 필드는 전역 변수 뒤에도 초기화됩니다.

 

일반적으로 문제가 해결됩니다. 추가 정적 플래그의 도움으로 변수가 초기화되었는지 확인합니다. 그렇지 않은 경우 이전에 저장된 값 목록에서 찾으십시오. 거기에 없으면 거기에 추가하십시오. 매크로에 싸인 모든 것 STATIC_SET

 class CData
{
 public : 
   string name;
};

template < typename T>
class CDataT : public CData
{
 public :
  T      value;
  CDataT( string name_, T const & value_) { name= name_;  value=value_; }
};


class CDataArray
{
  CData*_data[];
 public : 
  ~CDataArray()                    { for ( int i= 0 ; i< ArraySize (_data); i++) delete _data[i]; }
  
  CData* AddPtr(CData* ptr)        { int i= ArraySize (_data); return ArrayResize (_data, i+ 1 )> 0 ? _data[i]=ptr : NULL ; }
  CData* GetPtr( string name) const { for ( int i= 0 ; i< ArraySize (_data); i++) if (_data[i].name==name) return _data[i];   return NULL ; }
   template < typename T>
   bool Set( string name, T const &value){ CData* ptr= new CDataT<T>(name, value);   return ptr!= NULL && AddPtr(ptr)!= NULL ; }   
   template < typename T>   // Данная перегрузка необходима для передачи указателей, ибо баг в MQL
   bool Set( string name, T &value)      { CData* ptr= new CDataT<T>(name, value);   return ptr!= NULL && AddPtr(ptr)!= NULL ; }   
   template < typename T>
   bool Get( string name, T &value) const
                                   { CDataT<T>* ptr= dynamic_cast <CDataT<T>*> ( GetPtr(name) );
                                     if (ptr) value= ptr.value;
                                     return ptr!= NULL ;
                                   }
 private : 
   template < typename T>
   bool Get( string name, T const &value) const ; // =delete;
}; 

CDataArray __GlobData;


#define __STATIC_SET(var, name, value) \
{ \
   static bool inited= false ; \
   if (!inited && !__GlobData.Get(name, var)) { \   // Если копия переменной ещё не сохранена, то сохраняем её
    var=value;   if (!__GlobData.Set(name, var)) MessageBox ( "Failed to set static var:  " +name, "Error" , 0 ); \
  }\  
  inited= true ; \
}

#define STATIC_SET (var, value) __STATIC_SET(var, __FUNCSIG__ + "::" + #var, value)



//--- Проверка ---


class __CPrint { public : __CPrint( string str) { Print (str); } } __Print( "=======" );


uint StaticTickCount()
{
   static uint tickcount = GetTickCount ();
   Print ( "mql static value: " ,tickcount);

   STATIC_SET (tickcount, GetTickCount ());
   Print ( "my static value: " ,tickcount);
   Sleep ( 50 );
   return tickcount;
}

uint TickCount= StaticTickCount();

void OnStart ()
{  
   Print ( "OnStart" );
  StaticTickCount();
}

결과:

mql static value: 0
my static value: 940354171
OnStart
mql static value: 940354218
my static value: 940354171

 
fxsaber :

이것은 MQL5의 표준 동작입니다. 정적 변수는 전역 변수 다음에 작동하기 시작합니다.

당신은 이것에 대해 매우 진지해질 수 있습니다.

MQL의 정적 변수는 C++에서와 같이 선언 위치가 아니라 전역 범위에서 초기화됩니다.

제 생각에는 무엇을 먼저 초기화할지, 정적이든 전역적이든(맛을 제외하고) 무엇을 초기화할지 차이가 없습니다. 어쨌든 피해자는 있을 것입니다.

상수로 초기화되는 정적 및 전역 변수 를 먼저 초기화한 다음 나머지는 모두 컴파일러가 찾은 순서대로 초기화하는 것이 더 정확할 것입니다(이것은 이미 계획에 있지만 불행히도 달력에는 없습니다).

그리고 이 순서는 C++ 순서와 다릅니다.
일반적으로 MQL의 컴파일은 두 단계로 이루어집니다. 첫째, 모든 전역 정의가 수집된 다음 함수 본문이 컴파일됩니다. 이것이 정적 변수가 전역 변수 다음에 풀에 추가되는 이유입니다.

 
Ilyas :

(이것은 이미 계획에 있지만 불행히도 일정에는 없습니다).

그리고 이것이 최우선 과제라고 생각합니다. 현재 상황에서 프로그램 실행 논리가 위반되기 때문에 프로그래밍 언어에서는 단순히 용납할 수 없습니다. 그리고 모든 종류의 종소리와 휘파람과 새로운 기능 - 이것은 부차적입니다.
 

정적 배열을 포함하여 코드를 완성했습니다. 이번에는 파일로 첨부하겠습니다.

코드에 사용할 매크로는 4가지입니다.

1) STATIC_SET(var, data) - 값 데이터를 정적 변수 var에 할당하거나(= 연산자를 통해) 데이터 배열 을 정적 var 배열에 복사합니다.

 static int a;
STATIC_SET(a, 10 );

static int arr[ 5 ];
const int data[]= { 1 , 2 , 3 , 4 , 5 };
STATIC_SET(arr, data);

2) STATIC_INIT(var, data) - 생성자 또는 연산자 =를 통해 데이터 값으로 var 변수를 초기화하거나 상수로 var 배열을 초기화합니다. - 중괄호로 지정되고 추가로 일반 대괄호로 둘러싸여 있습니다.

 static int arr[ 5 ];
STATIC_INIT(arr, ({ 1 , 2 , 3 , 4 , 5 }));

3) STATIC(type, var, value) - 정적 변수의 선언 및 초기화:

STATIC( int , a, 10 );

4) STATIC_ARR(type, var, values) - 정적 배열의 선언 및 초기화:

STATIC_ARR( int , arr, ({ 1 , 2 , 3 , 4 , 5 }));


동시에 처음 두 단락에서 정적 배열의 선언된 크기는 최소한 초기화 값의 수여야 한다는 점을 고려해야 합니다. 그렇지 않으면 오류가 발생합니다.

그리고 동적 배열은 매크로로 초기화할 수 없습니다. 일반 이니셜라이저가 함수에 도달할 때까지 크기를 변경할 수 없습니다.

파일:
StaticVar.mqh  12 kb
 
그리고 이 모든 것이 왜 필요한지에 대한 질문에 대해서는 다음과 같이 설명합니다. 이것은 우리 코드가 MQL 개발자나 다른 사람이 의도한 대로가 아니라 알고리즘에서 의도한 대로 정확하게 작동하기 위해 필요합니다.
 
Alexey Navoykov :
그리고 이 모든 것이 왜 필요한지에 대한 질문에 대해서는 다음과 같이 설명합니다. 이것은 우리 코드가 MQL 개발자나 다른 사람이 의도한 대로가 아니라 알고리즘에서 의도한 대로 정확하게 작동하기 위해 필요합니다.

또는 이 모든 것이 코드 작성을 더 쉽게 만들거나 단축하거나 최소한 오류로부터 보호할 수 있는 예를 보여줄 수 있습니까? 그리고 조언자나 지표에서 추상적 기능이 아니라 거래 현실에 최대한 가깝게 하십시오.

 
Alexey Viktorov :

또는 이 모든 것이 코드 작성을 더 쉽게 만들거나 단축하거나 최소한 오류로부터 보호할 수 있는 예를 보여줄 수 있습니까? 그리고 조언자나 지표에서 추상적 기능이 아니라 거래 현실에 최대한 가깝게 하십시오.

최대한 가깝다는 뜻인가요? 내가 특별히 당신을 위해 그러한 코드를 작성하기를 원하십니까, 아니면 제 프로젝트를 게시하기를 원하십니까? 필요없어

손가락으로 설명하겠습니다. 여기에 프로그램 또는 고문의 전역 개체가 있습니다. CExpert Expert; 또는 C 프로그램 프로그램; 기본적으로 어떻게든 내부적으로 기본적으로 초기화되며(많은 내부 개체 포함), 아마도 어딘가에서 이를 위해 보조 전역 함수가 사용되며 이러한 함수에는 정적 변수가 포함될 수 있습니다. 또한 작업은 정적 필드가 있는 클래스를 사용합니다. 이 클래스는 정적 변수에도 속하므로 이러한 클래스의 작업은 정적 필드의 값에 연결됩니다. 잘못된 필드 값 - 잘못 초기화된 클래스 개체입니다. 그런 다음 논리적 체인을 직접 계속하십시오. 그리고 가장 나쁜 것은 우리가 프로그램을 실행하는 동안에만 그것에 대해 배운다는 것입니다.

나는 처음부터 이 모든 것을 생각해 낸 것이 아닙니다. 초기화했어야 하는 깨진 포인터나 값으로 초기화되어야 하는 빈 배열이 있는 경우가 종종 있습니다. 그리고 계속해서 이 작은 것들을 샅샅이 뒤지고 MQ 개발자들이 채택한 초기화 알고리즘을 만족시키기 위해 코드를 다시 실행하는 것 - 순서에 지쳤습니다.

사실 이것은 버그일 뿐 다른 것은 아닙니다. 개발자가 변수 초기화 의 특정 순서를 채택했다면 이 순서에 따라 코드를 실행해야 하며 이를 우회해서는 안 됩니다.

이 모든 것이 익숙하지 않은 경우 설명 된 기능을 사용하지 않고 예를 들어 Peter Konov 스타일로 작성하면 깃발이 당신의 손에 있고 이러한 문제는 당신에게 영향을 미치지 않았으므로 축하합니다.

 
Alexey Navoykov :

나는 처음부터 이 모든 것을 생각해 낸 것이 아닙니다. 초기화했어야 하는 깨진 포인터나 값으로 초기화되어야 하는 빈 배열이 있는 경우가 종종 있습니다. 그리고 계속해서 이 작은 것들을 샅샅이 뒤지고 MQ 개발자들이 채택한 초기화 알고리즘을 만족시키기 위해 코드를 다시 실행하는 것 - 순서에 지쳤습니다.

나는 많은 통계 선언 상황을 겪었습니다. stat가 다시 선언된 경우 전역 수준(OnInit 이전)에서 초기화된 클래스의 필드입니다. 필드는 클래스 설명 바로 뒤에 있고 해당 인스턴스의 전역 변수를 선언하기 전에 정적 필드를 초기화하는 데 문제가 발생한 적이 없습니다(이 경우 전역으로 간주되고 클래스 인스턴스보다 먼저 초기화되기 때문에 제가 이해하는 바). 즉, 메서드와 함수 내에서 정적 변수 선언을 거부하기만 하면 되며 문제가 없습니다.

 
Ilya Malev :

나는 많은 통계 선언 상황을 겪었습니다. stat가 다시 선언된 경우 전역 수준(OnInit 이전)에서 초기화된 클래스의 필드입니다. 필드는 클래스 설명 바로 뒤에 있고 해당 인스턴스의 전역 변수를 선언하기 전에 정적 필드를 초기화하는 데 문제가 발생한 적이 없습니다(이 경우 전역으로 간주되고 클래스 인스턴스보다 먼저 초기화되기 때문에 제가 이해하는 바). 즉, 메서드와 함수 내에서 정적 변수 선언을 거부하기만 하면 되며 문제가 없습니다.

물론 제가 OOP와 친하지 않아서 명확하게 설명할 수는 없지만 그래도 말씀하신 부분은 정정하고 싶습니다. 메서드와 함수 내에서 정적 변수 선언을 완전히 거부할 수는 없지만 적어도 정적 변수가 있는 메서드나 함수로 다른 정적 변수를 초기화하지 마십시오.

 int a( int n)
{
 static int f= 7 ;
 return (f+=n);
}

void OnTick ()
{
 static int b=a( 9 );
}
이 예에서 정적 int b 변수가 먼저 초기화되지만 int a(int n) 함수 내부에 있는 정적 int f 변수는 아직 초기화되지 않았으므로 결과적으로 쓰레기가 됩니다.
사유: