'knowhow'에 해당되는 글 92건

HowTo: Visual C++ 성능 문제 해결 방법

본 문서의 정보는 다음의 제품에 적용됩니다.
  • Microsoft Visual C++, 32-bit Editions 5.0, 6.0
중요: 본 문서는 레지스트리 편집에 대한 정보를 수록하고 있습니다. 레지스트리를 편집하기 전에 문제가 발생할 경우를 대비해 레지스트리를 복원하는 방법을 알고 있어야 합니다. 레지스트리를 편집하는 방법에 대한 내용은 Regedit.exe의 "레지스트리 복원" 도움말 항목이나 Regedt32.exe의 "레지스트리 키 복원" 도움말 항목을 참고하십시오.

요약

본 문서는 다양한 상황에서 Visual C++ 성능을 향상할 수 있는 단계들을 문서화합니다. 다음의 모든 상황은 Visual C++ 6.0 과 일부는 Visual C++ 5.0 에도 적용됩니다.

추가 정보

경고: 레지스트리 편집기를 잘못 사용하면 심각한 문제가 발생할 수 있으며 문제를 해결하기 위해 운영 체제를 다시 설치해야 할 수도 있습니다. Microsoft는 레지스트리 편집기를 잘못 사용하여 발생하는 문제에 대해 그 해결을 보증하지 않습니다. 레지스트리 편집기의 사용에 따른 모든 책임은 사용자에게 있습니다.

레지스트리 편집 방법에 대한 자세한 내용은 레지스트리 편집기(Regedit.exe)의 "키 및 값 변경" 도움말 항목 또는 Regedt32.exe의 "레지스트리의 정보 추가 및 삭제" 및 "레지스트리 데이터 편집" 도움말 항목을 참조하십시오. 레지스트리를 편집하기 전에 레지스트리 파일을 백업해야 합니다. 또한 Windows NT나 Windows 2000을 실행하는 경우 ERD(응급 복구 디스크)를 업데이트해야 합니다.

Visual C++ 성능향상을 위한 제안

  • 모든 workspace 는 관련 .ncb and .opt 확장자를 가진 파일들을 가지고 있습니다. 이들 파일들은 충돌할 수 있습니다. 이 파일들을 만들기 위해서는 처음에 workspace를 닫아야 합니다. 그리고 나서 위의 두 파일을 삭제하거나 이름을 변경하기 위해서 workspace 디렉터리로 갑니다. Workspace를 다시 열면 이 파일들은 다시 만들어 질 것입니다. 여러분은 그 파일들이 만들어 질 때 지연 현상을 겪게 될 것입니다.

    주의: .ncb 파일은 ClassView, IntelliSense 그리고 WizardBar에 대한 정보를 가지고 있습니다. .opt 파일은 workspace 설정 파일입니다. .opt 파일을 지우는 것은 당시 Window Position, Breakpoints, 기타 Minor Settings의 손실을 초래할 것입니다.

    자세한 내용은 Microsoft 기술 자료의 다음 문서를 참조하십시오.
      Q173164 PRB: Loading a Workspace Is Slow in Visual C++
  • Visual C++ 6 에만 해당: IntelliSence 은 Visual C++ 를 느려지게 할 수 있습니다. 이것을 점검하고 해결하기 위해서는 도구 메뉴에서 옵션을 선택함으로써 IntelliSense을 사용 불가능하게 만드는 것입니다. 편집 탭에서 "Statement completion options" 그룹에 있는 모든 체크 박스를 해제합니다. 더 자세한 정보는 Microsoft 기술 자료에 있는 다음의 문서를 참조하십시오.
      Q153284 INFO: Limitations of IntelliSense in Visual C++ 6.0
  • Visual C++ integrated development environment (IDE)와 통합된 Microsoft Visual SourceSafe와 같은 소스 코드 제어 소프트웨어는 Visual C++ 가 시작하는 동안에 source code server에 연결하도록 설정될 수 있습니다. 그러한 경우에 네트워크 연결의 손실은 Visual C++ 이 매우 느리게 시작하는 원인이 될 수 있습니다. 성능을 향상 시키려면 적절한 네트워크 연결 또는 Visual C++ IDE에서 소스 코드 제어 소프트웨어 통합을 사용 불가능하도록 하는 것입니다. 후자를 선택하시려면 Visual C++을 종료하고 나서 다음의 레지스트리 키를 입력하기 위해서 RegEdit.Exe을 사용하고 Disabled 값을 (DWORD) 0x00000001 로 설정하는 것입니다: HKEY_CURRENT_USER\Software\Microsoft\DevStudio\6.0\Source Control\Disabled주의: 레지스트리 키들을 수정하기 이전에 Visual C++를 종료하였는지 확인하시기 바랍니다. 그렇지 않으면 변경된 값은 적용되지 않을 것입니다.
  • 소스 코드 제어 소프트웨어는 소스 코드 제어 하에서 Visual C++ 프로젝트들에 대한 Background Status 업데이트들을 수행할 수 있습니다. 이러한 특성이 사용 가능하다면 이들 업데이트들은 IDE에서 응답을 느려지도록 할 수 있습니다. 이들 background update들을 사용하지 못하도록 하려면 도구 메뉴에서 옵션을 선택합니다. 그리고 나서 Source Control 탭을 클릭하고 Perform Background Status Updates 체크박스를 해제합니다. 더 자세한 정보는 Microsoft 기술 자료에 있는 다음의 문서를 참조하십시오.
      Q195375 FIX: ClassView Can Be Very Slow When SourceSafe Is Being Used
  • 64개의 네트워크 디렉터리보다 적은 곳으로 여러분의 파일을 옮기시기 바랍니다. 이것은 Visual C++가 사용하는 방법이므로 64번째와 위의 디렉터리들을 매 5분마다 검색하는 file change notifications를 얻는데 도움을 줍니다.

    더 자세한 정보는 Microsoft 기술 자료에 있는 다음의 문서를 참조하십시오.
      Q216098 PRB: Visual C++ IDE Slows When Files Are in Many Directories
  • Source editor 와 Resource editor 를 사용할 때 WizardBar는 성능문제를 일으킬 수 있습니다. WizardBar를 사용 불가능하게 하는 것과 Visual C++를 재시작 하는 것은 성능을 향상시킬 수 있습니다. 더 자세한 정보는 Microsoft 기술 자료에 있는 다음의 문서를 참조하십시오.
      Q202591 BUG: Developer Studio Source and Dialog Editors Respond Slowly
      Q170511 PRB: WizardBar Notification Slows Down Dialog Editor
  • 많은 수의 자원을 resource-only DLL 으로 나누는 것은 IDE 의 성능을 향상시킬 수 있습니다. 여러분은 AppWizard에 의해서 만들어진 MFC 응용 프로그램에 약 100여 개의 다이얼로그를 추가함으로써 이것을 확인하실 수 있습니다. ClassWizard를 사용하여 그 다이얼로그들 내에 있는 어떠한 컨트롤이라도 코드와 associate 하는 것은 그 다이얼로그에 대한 성능 문제을 초래할 것입니다.

    프로젝트를 시작하기 이전에 Resource-only DLL 내부에서 번역된 리소스(localized resources)를 보관하도록 여러분의 프로젝트를 설계하는 것을 고려하여야 합니다. 자세한 정보를 보시려면 MSDN Online Libraries(http://msdn.microsoft.com/resources/libraries.asp) 내에 있는 다음의 MFC 기술 노트를 참고하십시오. 자원을 그룹화하는 것과 그들을 하나의 파일에 두는 것 보다 각각 리소스 파일들(.rc)에 위치시키는 것을 고려해야 할 수도 있습니다. 보다 자세한 정보를 보시려면 MSDN Online Libraries 내에 있는 다음의 MFC 기술 노트를 참고하시기 바랍니다
  • Visual C++ 6 에만 해당: 디버그 세션 동안에 소스 코드 편집은 느릴 수도 있습니다. 편집을 불가능하게 하고 Continue 옵션은 성능을 향상시킬 수도 있습니다. 그렇게 하기 위해서는 도구 메뉴에서 Options 을 선택합니다. Debug 탭에서 Debug Commands Invoke EditContinue 체크 박스를 선택하지 않습니다. Edit and Continue 컴파일러 스위치 /ZI 를 사용하는 것을 피해야 하는 것 대신에 /Zi 를 사용할 수도 있습니다.
  • IDE내에서 실행되는 모든 add-ins 과 매크로에 대해서 잘 알고 있어야 합니다. 왜냐하면 process-intensive, 잘못 설계된 매크로, add-ins 는 Visual C++를 느려지게 하기 때문입니다. 이러한 것을 점검하기 위해서는 도구 메뉴에서 Customize를 선택합니다. 로드 된 add-ins 과 매크로는 모두 Add-ins and Macro Files 탭에 나열되어 있습니다. 이들이 성능을 떨어뜨리는지 테스트하기 위해서는 모든 add-ins 와 매크로에 대해서 체크 박스를 선택하지 않습니다. 이들 add-ins 과 매크로가 로드 되지 않았는지 확인하기 위해서 Visual C++를 종료하였다가 다시 구동 시킵니다.
  • 마이크로소프트 운영 체제가 아닌 다른 운영 체제에서 remote 상에 파일들이 위치하게 되는지 확인하시기 바랍니다. 여러분의 네트워크 클라이언트 소프트웨어가 적절하게 설정되어 있는지 확인하시기 바랍니다. 증가한 네트워크 트래픽은 성능에 영향을 줄 수도 있습니다.
  • Project 종속 관계 또한 빌드하는데 소요되는 시간을 증가시킬 수도 있습니다. 더 자세한 정보는 Microsoft 기술 자료에 있는 다음의 문서를 참조하십시오.
      Q229030 BUG: A Workspace with Many Dependencies May Appear to Hang
    프로젝트 파일들을 생성하는 Exporting 은 이러한 경우에 느려질 수 있습니다.
  • 파일에 대한 긴 경로는 성능을 저하 시킬 수도 있습니다, Visual C++ 가 Include 파일들의 위치를 검색해야 하는 경로가 많을수록 compilation은 오래 걸릴 것입니다. 네트워크 경로를 사용하는 것 역시 성능문제의 원인이 될 수 있습니다. 프로젝트에서 지정한 검색 경로를 찾는 것은 다음의 단계들을 따르시기 바랍니다.

    1. 프로젝트 메뉴에서 설정을 클릭하시기 바랍니다.
    2. 설정 대화 상자에서 C/C++ 탭을 선택하시기 바랍니다.
    3. Category 의 드롭다운 목록에서 Preprocessor 를 선택하시기 바랍니다.
    4. Project-specific include paths는 부가적인 include 디렉터리 편집 상자 내에 존재합니다.
    전체적으로 Visual C++ 에 적용되는 검색 경로를 찾기 위해서는 다음의 단계들을 따르시기 바랍니다.
    1. 도구 메뉴상에서 옵션을 클릭합니다.
    2. 옵션 대화 상자에서 디렉터리 탭을 선택하시기 바랍니다.
    3. Show directories for list 에서 Include files 를 선택합니다.
  • 시스템 가상 메모리와 하드 디스크가 부족할 수도 있습니다. 필요하다면 RAM을 추가하거나 대용량의 하드 디스크를 추가함으로써 성능을 개선할 수도 있습니다.

    Windows NT 가 실행 중인 컴퓨터들의 메모리 사용을 모니터하기 위해서는 작업 관리자를 사용하시기 바랍니다. Windows 95 또는 Windows 98 이 실행 중인 컴퓨터들 상에서는 Resource Meter를 사용할 수도 있습니다. Resource Meter를 설치하기 위해서는 제어판에 있는 프로그램 추가/ 삭제에서 Windows 설정 탭을 선택하고 시스템 도구를 두 번 누릅니다. Resource Meter 는 도구들 중에 나열되어 있습니다. 체크 박스에 표시를 하고 Resource Meter를 설치하기 위해서 확인을 누릅니다. Visual C++ 에서 제공되는 Process Viewer (PView.exe)도 사용할 수 있습니다.
  • 다른 프로그램 또는 서비스들은 귀중한 CPU 시간과 메모리를 활용하고 있을 수도 있습니다. 그런 경우 일시적으로 시스템 시작 폴더에서 제거하고 해당 컴퓨터를 재시작 할 수도 있습니다. Windows NT가 실행중인 시스템에서는 하나 이상의 시작 그룹이 존재할 수도 있습니다. 그런 모든 시작 그룹을 비우시기 바랍니다. CPU와 메모리의 활용을 모니터 하기 위해서 이전 단계에서 언급한 도구를 사용할 수도 있습니다.

    사용자 정의(Customizing)와 시작 그룹(Startup group) 비활성화에 대한 더 자세한 정보는 Microsoft 기술 자료에 있는 다음의 문서를 참조하십시오.
      Q152122 How to Customize the Start or Programs Menu
      Q81606 Disabling the Startup Group in Windows
  • Background에서 실행 중인 Antivirus 프로그램은 여러분이 작업하는 파일을 스캐닝 할 수 있습니다. 바이러스 스캐너를 일시적을 사용하지 못하도록 하면 Visual C++ 성능을 향상시켜 줍니다. 그러므로 보다 적게 검색하도록 바이러스 스캐너를 설정하거나 최근의 업데이트에 관해서 소프트웨어 제조 업체로 문의하시기 바랍니다.
  • 불량 드라이버는 성능문제의 원인이 될 수도 있으며 잠재적으로는 repainting delays 의 원인이 될 수도 있습니다. 성능문제의 원인이 될 가능성이 있는 디스플레이 드라이버를 제거하시기 바랍니다. Windows NT 가 실행 중인 컴퓨터들 상에서 Windows NT VGA 모드에서 해당 문제를 재현해보시기 바랍니다. Windows 95 또는 Windows 98 이 실행 중인 컴퓨터들 상에서 디스플레이 어뎁터를 제어판내의 디스플레이 어플리케이션을 사용하여 표준 VGA 로 바꾸시기 바랍니다. 이들 모드에서 서드 파티 디스플레이 드라이버들은 사용 불가능하게 됩니다.

    드라이버에 오류가 있는 경우 업데이트 된 버전을 구해보시기 바랍니다. 일시적인 대처 방안은 디스플레이 설정을 바꾸는 방법도 있습니다. 제어판으로부터 디스플레이 프로그램을 시작하고 설정 탭을 선택합니다. 데스크 톱 해상도, 화면 주사율, 시스템에서 사용되는 색상의 수를 변경하시기 바랍니다.
블로그 이미지

요다할아범

,

friend 함수

knowhow/Visual C++ 2007. 8. 24. 10:21

* 객체 지향의 특징


1. 객체 중심의 프로그램방식


꼭 필요한 인터페이스만 외부로 노출하고 세부 구현은 숨기는 추상성의 조건도 만족해야 한다. 그래야 최소한의 정보만으로 객체를 쉽게 사용할 수 있으며 부주의한 사용자로부터 자신을 방어할 수도 있다.

 

캡슐화를 완성하고 추상성의 조건을 만족하여 완벽한 부품이 되기 위해 자신의 정보를 적당히 숨겨야 하며 이것이 정보 은폐의 개념이다. 정보 은폐는 캡슐화의 범주에 속하면서 동시에 추상화를 위한 필요조건이기도 하다.

 

     => 객체 중심으로 프로그램을 하고 클래스의 메서드 만을 이용하여 프로그램을 접근한다.

 

1) 정보 은폐의 개념

 

정보 은폐의 개념을 가장 쉽게 단 한마디로 표현하면 "몰라도 된다"는 것이다. 부품을 쓰는 사용자가 알아야 하는 것은 부품을 사용하는 방법뿐이지 부품의 내부 동작이나 상세 구조가 아니다. 사용자가 굳이 알 필요가 없는 불필요한 정보는 숨김으로써 사용자는 최소한의 정보만으로 부품을 쉽게 사용할 수 있게 된다.

 

   예) 자동차 운전과 엔진의 관계

 

   예) 이미지 Jpeg 뷰어 클래스

 

class JpegImage

{

private:                              // 내부적으로 숨겨진 몰라도 되는것

     BYTE *RawData;

     JPEGHEADER Header;

     void DeComp();

     void EnComp();

 

public:                               // 외부로 오픈된 인터페이스  - 읽고 저장하고 그리기만하면된다.

     Jpeg();

     ~Jpeg();

     BOOL Load(char *FileName);

     BOOL Save(char *FileName);

     void Draw(int x, int y);

};

 

   // 사용 예제

 

  JpegImage J;

  J.Load("c:\\Image\\PrettyGirl.jpg");

  J.Draw(10,10);

 

사용자가 이 클래스를 쓰기 위해 알아야 하는 것은 오로지 Load, Save, Draw등 공개된 멤버 함수뿐이다. 이 함수들이 바로 인터페이스 함수들이며 최소한의 인터페이스만 공개하는 것이 추상화의 정의이다.

 

 

2) 정보 은폐를 위한 클래스의 지원 기능 => 접근 지정자

 

C++은 클래스의 정보 은폐 기능을 지원하기 위해 private, public, protected 등의 액세스 지정자를 제공하며 액세스 지정자로 숨길 멤버와 공개할 멤버의 블록을 구성하도록 해 준다.

공개된 멤버는 외부에서 자유롭게 읽을 수 있지만 숨겨진 멤버를 참조하려고 시도하면 컴파일 과정에서 접근할 수 없다는 에러로 처리된다.

 

3) 왜 정보 은폐를 해야 하나 ?  사용자가 알아서 하게 하면 않되나 ?

 

JpegImage 클래스의 경우도 마찬가지이다. RawData 멤버에 압축을 풀기전의 이미지 정보가 저장되어 있는데 이 값을 사용자가 직접 조작하도록 내버려 두면 이미지가 손상될 위험이 있다.

압축을 해제하는 DeComp 함수는 이미지 데이터의 구조 판별과 헤더 분석이 끝나야만 호출할 수 있는데 사용자가 이런 주의 사항을 모르고 아무렇게나 DeComp를 호출하도록 허락해서는 안된다.

사용자는 공개된 멤버를 통해 의사 표현만 하고 객체는 지시대로 서비스하는 것이 합리적이다.


4) 정보 은폐의 방법

  [ 기본 공식 ]    멤버 변수는 숨기고 멤버 함수를 공개 한다.


모든 객체에 적합한 정보 은폐 공식은 없지만 대충의 가이드라인을 제시해 보면 이렇다. 멤버 변수는 객체의 상태를 저장하는 중요한 정보들이므로 외부에서 함부로 변경하지 못하도록 숨기고 멤버 함수는 외부와 인터페이스를 이루는 수단이므로 공개한다. 숨겨진 멤버 변수는 공개된 멤버 함수를 통해 정해진 방법으로만 액세스하도록 하는 것이 보통이다


: InfoHide

#include <Turboc.h>

 

class Student

{

private:                                   // 객체의 숨기기

     int StNum;

     char Name[16];

     unsigned Score;

     BOOL TestScore(int aScore) {

          return (aScore >= 0 && aScore <= 100);

     }

 

public:                                     // 객체의 공개

     Student(int aStNum) { StNum=aStNum;Name[0]=0;Score=0; }

     int GetStNum() { return StNum; }

     char *GetName() { return Name; }

     void SetName(char *aName) { strncpy(Name,aName,15); }

     unsigned GetScore() { return Score; }

     void SetScore(int aScore) {

          if (TestScore(aScore))

              Score=aScore;

     }

};

 

void main()

{

     Student Kim(8906123);

     Kim.SetName("김천재");

     Kim.SetScore(99);

     printf("학번=%d, 이름:%s, 점수:%d\n",

          Kim.GetStNum(),Kim.GetName(),Kim.GetScore());

}

 




2. 프랜드 함수


=> 예외 적인 정보 접근의 허용

  이유 : C++의 액세스 지정자는 너무 엄격해서 일단 숨기면 정상적인 문법으로는 외부에서 이 멤버를 참조할 수 없다. 어떤 경우에는 이런 정보 은폐 기능이 방해가 될 수도 있기 때문에 예외적으로 지정한 대상에 대해서는 모든 멤버를 공개할 수 있는데 이를 프렌드 지정이라고 한다.



1) 프랜드 속성의 지정 방식

프렌드는 전역 함수, 클래스, 멤버 함수의 세가지 수준에서 지정할 수 있다


다음은 func 함수를 Some 클래스의 프렌드로 지정한 것이다.

 

class Some

{

     friend void func();

     ....

};

 

=> Some 클래스 선언부에서 func 함수를 프렌드로 지정했으므로 이 클래스의 모든 멤버를 자유롭게 액세스할 수 있는 특권이 부여된다. private영역에 있건 public 영역에 있건 어떤 멤버 변수든지 읽고 쓸 수 있으며 모든 멤버 함수를 자유롭게 호출할 수 있다.

 

 

2) 프랜드 함수의 사용

 

=>OutToday함수는 이 두 클래스의 객체를 인수로 전달받아 날짜와 시간을 동시에 출력한다. 그러기 위해서 OutToday는 양쪽 클래스의 모든 멤버를 읽을 수 있어야 하는데 Date나 Time의 멤버 함수로 포함되면 한쪽밖에 읽을 수 없게 된다

한 함수가 동시에 두 클래스의 멤버 함수가 될 수는 없다.

 

=> 이럴 때 OutToday를 멤버 함수가 아닌 전역 함수로 정의하고 양쪽 클래스에서 이 함수를 프렌드로 지정하면 된다. 이렇게 되면 OutToday는 Data의 year, month, day와 Time의 hour, min, sec을 모두 읽을 수 있다. 마치 양쪽 클래스의 멤버 함수인 것처럼 숨겨진 멤버를 자유롭게 액세스한다

 

 

: FriendFunc

#include <Turboc.h>

 

class Date;

class Time

{

    friend void OutToday(Date &,Time &);                    // 외부 함수를 프랜드로 정의

private:

     int hour,min,sec;

public:

     Time(int h,int m,int s) { hour=h;min=m;sec=s; }

   };

 

class Date

{

     friend void OutToday(Date &,Time &);

private:

     int year,month,day;

public:

     Date(int y,int m,int d) { year=y;month=m;day=d; }

};

 

void OutToday(Date &d, Time &t)

{

     printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

          d.year,d.month,d.day,t.hour,t.min,t.sec);

}

 

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     OutToday(D,T);

}

 

 

=> 프랜드 방식만이 접근이 가능한가 ? 아니다

이 예제의 경우 프렌드만이 문제를 해결하는 유일한 방법은 아니다. 프렌드 지정을 하는 대신 각 클래스에 숨겨진 멤버를 대신 읽어주는 Get, Set 액세스 함수를 공개 영역에 작성하고 OutToday는 액세스 함수들로 값을 읽을 수도 있다. 하지만 이런 방법이 번거롭고 귀찮기 때문에 프렌드 지정이라는 좀 더 간편한 방법을 사용하는 것이다.

 

 

3. 프랜드 클래스

 

=>

두 클래스가 아주 밀접한 관련이 있고 서로 숨겨진 멤버를 자유롭게 읽어야 하는 상황이라면 클래스를 통째로 프렌드로 지정할 수 있다. 클래스 선언문내에 프렌드로 지정하고 싶은 클래스의 이름을 적어주면 된다.

 

예) Any 클래스를 Some 클래스의 프렌드로 지정하는 것이다.

 

class Some

{

     friend class Any;

     ....

};

 

 

: FriendClass

#include <Turboc.h>

 

class Time

{

     friend class Date;

private:

     int hour,min,sec;

public:

     Time(int h,int m,int s) { hour=h;min=m;sec=s; }

};

 

class Date

{

private:

     int year,month,day;

public:

     Date(int y,int m,int d) { year=y;month=m;day=d; }

     void OutToday(Time &t)

     {

          printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

              year,month,day,t.hour,t.min,t.sec);

     }

};

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     D.OutToday(T);

}

 

예) 프랜드 클래스의 적용 사례

 

MFC 라이브러리의 경우 다음과 같은 프렌드 클래스의 예가 많이 있으며

 

CDocument가 CView의 프렌드

CTime이 CTimeSpan의 프렌드

CToolBar가 CToolTipCtrl의 프렌드

CPropertySheer가 CPropertyPage의 프렌드

 

모두 아주 밀접한 관계에 있는 클래스들인데 MFC의 구조를 공부해 보면 이 클래스들이 왜 프렌드여야 하는지 알게 될 것이다.

CView와 CDocument는 하나의 실체에 대해 각각 외부와 내부를 다루는 관련있는 클래스이다.

CView는 자신이 화면에 출력할 데이터를 읽기 위해 CDocument의 멤버를 마음대로 읽을 수 있어야 하며

CToolTipCtrl 클래스는 툴팁을 보여줄 버튼이나 영역을 구하기 위해 CToolBar의 멤버를 액세스해야 한다.

 

 

4. 클래스내의 프랜드 멤버함수

 

=>프렌드 멤버 함수는 특정 클래스의 특정 멤버 함수만 프렌드로 지정하는 것이며 꼭 필요한 함수에 대해서만 숨겨진 멤버를 액세스하도록 범위를 좁게 설정할 수 있는 장점이 있다.

개념은 프렌드 함수와 동일하되 다른 클래스에 속한 멤버 함수라는 것만 다르다. 클래스 선언부에 프렌드로 지정하고자 하는 멤버 함수의 원형을 friend 키워드와 함께 적어주면 된다. 다음 예는 Any::func 멤버 함수를 Some 클래스의 프렌드로 지정한다.

 

class Some

{

     ....

    friend void Any::func(Some &S);

};

 

이렇게 선언하면 Any클래스의 func 멤버 함수는 Some 클래스의 모든 멤버를 액세스할 수 있다.

 

=> 순서에 주위

     프렌드 멤버 함수는 프렌드로 지정되는 클래스 소속이며 통상 대상 클래스를 인수로 전달받기 때문에 프렌드 지정을 포함하는 클래스를 먼저 선언하고 프렌드 멤버 함수를 포함한 클래스를 전방 선언해야 한다.

 

 

: FriendMem

#include <Turboc.h>

 

class Time;

class Date

{

private:

     int year,month,day;

public:

     Date(int y,int m,int d) { year=y;month=m;day=d; }

     void OutToday(Time &t);

};

 

class Time

{

     friend void Date::OutToday(Time &t);

private:

     int hour,min,sec;

public:

     Time(int h,int m,int s) { hour=h;min=m;sec=s; }

};

 

void Date::OutToday(Time &t)

{

     printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

          year,month,day,t.hour,t.min,t.sec);

}

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     D.OutToday(T);

}

 

 

 [ 프랜드 클래스이 주의사항 ]

 

 1) 프렌드 지정은 단방향이며 명시적으로 지정한 대상만 프렌드가 된다. A가 B를 프렌드로 선언했다고 하더라도 B가 A를 프렌드로 선언하지 않으면 A는 B의 프렌드가 아니다. 그래서 B는 A의 모든 멤버를 읽을 수 있지만 A는 그렇지 못하다.

 

2) 프렌드 지정은 전이되지 않으며 친구의 친구 관계는 인정하지 않는다.

A는 B를 프렌드 선언했고 B는 C를 프렌드 선언했다. 그래서 B는 A를 마음대로 액세스할 수 있으며 C도 B를 마음대로 액세스할 수 있다. 그러나 C는 A의 숨겨진 멤버를 액세스할 수 없다

 

 

3)복수의 대상에 대해 동시에 프렌드 지정을 할 수 있지만 한번에 하나씩만 가능하다. A가 B, C를 동시에 프렌드로 지정하고 싶을 때 다음처럼 해야 한다.

 

class A

{

     friend class B;

     firend class C;

     ....

 

4) 프렌드 관계는 상속되지 않는다. A가 B를 프렌드로 지정하면 B는 A를 액세스할 수 있다. 그러나 B로부터 파생된 D는 A의 프렌드가 아니므로 A를 마음대로 액세스할 수 없다.

 

 

[ 프랜드는 클래스 은폐와 추상화의 본연의 의미와 반하나 ]

 

어떻게 생각하면 프렌드는 은폐된 정보의 기밀성을 완전히 무시해 버리므로 무척 위험한 장치처럼 생각될 수도 있다. 그러나 개발자의 명시적인 지정에 의해서만 예외를 인정하며 그것도 클래스의 선언부에서만 지정할 수 있고 클래스 외부에서는 지정할 수 없으므로 위험하지는 않다. 프렌드 지정에 의해 공개되는 범위는 지정한 클래스나 함수에 국한되므로 전면적인 공개와는 성질이 다르다

 

 

 

5. this (호출한 객체의 번지를 가리키는 포인터 상수이다)

 

=> 객체별 자신을 가리키는 포인터다

 

예) 단순 클래스로 본 this의 의미

 

: this

#include <Turboc.h>

 

class Simple

{

private:

     int value;

 

public:

     Simple(int avalue) : value(avalue) { }

     void OutValue() {

          printf("value=%d\n",value);

     }

};

 

void main()

{

     Simple A(1), B(2);

     A.OutValue();

     B.OutValue();

}

 

 [ 문제 제시 ]

=>OutValue 함수가 자신을 호출한 객체를 어떻게 아는가 하는 점이다.

이 함수의 본체 코드에서 value라는 멤버를 이름만으로 참조하고 있는데 이 멤버가 과연 누구의 value인지 어떻게 판단하는가? 함수는 호출원으로부터 정보를 전달받을 때 인수를 사용하는데 OutValue 함수의 원형을 보면 어떠한 인수도 받아들이지 않는다. 입력값인 인수가 없으면 함수의 동작은 항상 같을 수밖에 없음에도 불구하고 OutValue는 호출한 객체에 따라 다른 동작을 할 수 있다.

 

=> 그 이유를 설명하자면 main에서 OutValue를 호출할 때 어떤 객체 소속의 멤버 함수를 호출할 것인지 소속 객체를 함수 이름앞에 밝혀 주었기 때문이다.

 코드를 보면 A.OutValue();B.OutValue(); 식으로 작성되어 있어 사람이 눈으로 보기에도 두 호출문은 구분된다.

그러나 함수의 입장에서는 자신을 호출한 문장앞에 붙어 있는 A. , B. 따위의 객체 이름을 읽을 수 없으며 인수로 전달되는 값만이 의미가 있다.

 

 

=> A.OutValue() 호출문은 컴파일러에 의해 다음과 같이 재해석된다.




 =>  멤버 함수를 호출할 때 호출한 객체의 정보가 함수에게 암시적으로 전달된다는 것이다. 그래서 멤버 함수는

    호출한 객체별로 다른 동작을 할 수 있고 복수 개의 객체가 멤버 함수를 공유할 수도 있게 된다.


=>우리 눈에 명시적으로 보이지는 않지만 OutValue 함수는 자신을 호출한 객체의 번지를 인수로 전달받는다.

        이때 전달받은 숨겨진 인수를 this라고 하는데 호출한 객체의 번지를 가리키는 포인터 상수이다


=> this->value는 B 객체의 value멤버를 의미한다.

멤버 함수가 객체들에 의해 공유되려면 호출한 객체를 구분해야 하고 그러기 위해서는 호출 객체의 정보를 함수의 인수로 전달해야 하는데 이 처리를 개발자가 직접해야 한다면 무척 귀찮을 것이다. 이 작업은 모든 멤버 함수에 공통적으로 필요한 조치이며 획일적이기 때문에 개발자가 별도로 명시하지 않아도 컴파일러가 알아서 자동으로 해 주도록 되어 있다.




6. 정적 멤버 변수


=> 개념 :  클래스의 바깥에 선언되어 있지만 클래스에 속하며 객체별로 할당되지 않고 모든 객체가 공유하는 멤버이다


1) 전역 카운트 변수를 사용 - 비 클래스적인 접근


: ObjCount

#include <Turboc.h>

 

int Num=0;                  // 전역 변수 선언

 

class Count

{

private:

     int Value;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

 

void main()

{

     Count C,*pC;

     C.OutNum();

 

     pC=new Count;

     pC->OutNum();

 

     delete pC;

 

     C.OutNum();

 

     printf("크기 = %d\n",sizeof(C));

}

 

 => 정적이든 동적이든 객체가 생성, 파괴될 때는 생성자와 파괴자가 호출되며 이 함수들이 Num을 관리하고 있으므로 Num은 항상 생성된 객체의 개수를 정확하게 유지한다. 디버거로 한 줄씩 실행해 가면서 Num 변수의 값을 관찰해 보면 이 변수가 생성된 객체수를 정확하게 세고 있음을 확인할 수 있다. 애초에 원하는 목적은 달성했지만 이 예제는 전혀 객체 지향적이지 못하다.

 

[ 문제점 ]

 

1) 클래스와 관련된 중요한 정보를 왜 클래스 바깥의 전역 변수로 선언하는가가 일단 불만이다

2) 또한 전역 변수는 은폐할 방법이 없기 때문에 외부에서 누구나 마음대로 집적거릴 수 있다. 어떤 코드에서 고의든 실수든 Num=1234; 라고 대입해 버리면 생성된 객체수가 1234개라고 오판하게 될 것이다.



2) 전역 변수를 클래스화


 Num을 Count 클래스안에 캡슐화해야 한다.


class Count

{

private:

     int Value;

    int Num;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};


=> 문제는 변수를 클래스 별로 가진다는 점 , 전역 적인 역활수행 불가



3) 정적 멤버 변수의 사용


Num은 클래스의 멤버이면서 클래스로부터 생성되는 모든 객체가 공유하는 변수여야 한다. 이것이 바로 정적 멤버 변수의 정의이며 이 문제를 풀 수 있는 유일한 해결책이다.


예)


class Count

{

private:

     int Value;

    static int Num;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

int Count::Num=0;  

int Count::Num;                // 초기화 해주지 않아도 0으로 초기화 된다.


=> 선언 방식

 클래스 내부의 선언은 Num이 Count 클래스 소속이며 정수형의 정적 멤버 변수라는 것을 밝히고 외부의 정의는 Count에 속한 정적 멤버 Num을 선언하고 0으로 초기화한다는 뜻이다. 외부 정의에 의해 메모리가 할당되며 이때 초기값을 줄 수 있다.

 

=> 특징

1) 정적 멤버 변수는 객체와 논리적으로 연결되어 있지만 객체 내부에 있지는 않다.

2) 정적 멤버 변수를 소유하는 주체는 객체가 아니라 클래스이다.

3) 객체 크기에 정적 멤버의 크기는 포함되지 않으며 sizeof(C) = sizeof(Count)는 객체의 고유 멤버 Value의 크기값인 4가 된다.

 

 

7. 정적 멤버 함수 => 정적 변수와 함수접근이 가능하다.

 

=> 객체와 직접적으로 연관된다기보다는 클래스와 연관되며 생성된 객체가 하나도 없더라도 클래스의 이름만으로 호출할 수 있다.

일반 멤버 함수는 객체를 먼저 생성한 후 obj.func() 형식으로 호출한 객체에 대해 어떤 작업을 한다.

이에 비해 정적 멤버 함수는 Class::func() 형식으로 호출하며 클래스 전체에 대한 전반적인 작업을 한다. 주로 정적 멤버 변수를 조작하거나 이 클래스에 속한 모든 객체를 위한 어떤 처리를 한다.

 

예)  정적 멤버 함수의 사용 - 정적 변수에 대한 제어를 클래스 생성전에 가능하다.

 

: ObjCount2

#include <Turboc.h>

 

class Count

{

private:

     int Value;

    static int Num;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     static void InitNum() {

          Num=0;

     }

     static void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

int Count::Num;

 

void main()

{

     Count::InitNum();           // 클래스 생성 전에 접근 가능

     Count::OutNum();

     Count C,*pC;

     C.OutNum();

     pC=new Count;

     pC->OutNum();

     delete pC;

     pC->OutNum();

     printf("%d",sizeof(C));

}

 

=> 비 정적 변수에 접근이 않된다. (생성전에)

static void InitNum()

{

      Num=0;     

     Value=5;           // 접근 불가

}

 

 

9. 정적 멤버의 활용 용도

 

1)  단 한번만 해야 하는 전역 자원의 초기화

데이터 베이스 연결이나 네트웍 연결, 윈도우 클래스 등록 등과 같이 단 한번만 하면 되는 초기화는 정적 멤베 함수에서 하고 그 결과를 정적 멤버 변수에 저장한다. 이런 전역 초기화는 일반적으로 두 번 할 필요도 없고 두 번 초기화하는 것이 허용되지도 않는다. 그래서 객체별로 초기화해서는 안되며 클래스 수준에서 딱 한번만 초기화하고 그 결과는 모든 객체가 공유한다.

 

예) DB 서버에 연결하는 과정은 굉장히 느리고 리소스를 많이 차지하기 때문에 객체별로 따로 연결하지 않고 딱 한번만 연결해야 한다. 이럴 때 사용하는 것이 바로 정적 멤버이다.

 

 

: GlobalInit

#include <Turboc.h>

 

class DBQuery

{

private:

     static HANDLE hCon;

     int nResult;

 

public:

     DBQuery() { };

     static void DBConnect(char *Server, char *ID, char *Pass);

     static void DBDisConnect();

     BOOL RunQuery(char *SQL);

     // ....

};

HANDLE DBQuery::hCon;

 

void DBQuery::DBConnect(char *Server, char *ID, char *Pass)

{

     // 여기서 DB 서버에 접속한다.

     // hCon = 접속 핸들

}

 

void DBQuery::DBDisConnect()

{

     // 접속을 해제한다.

     // hCon=NULL;

}

 

BOOL DBQuery::RunQuery(char *SQL)

{

     // Query(hCon,SQL);

     return TRUE;

}

 

void main()

{

     DBQuery::DBConnect("Secret","Adult","doemfdmsrkfk");

     DBQuery Q1,Q2,Q3;

 

     // 필요한 DB 질의를 한다.

     // Q1.RunQuery("select * from tblBuja where 나랑 친한 사람");

 

     DBQuery::DBDisConnect();

}

 

 

2)  읽기 전용의 자원 초기화

객체는 스스로 동작할 수 있지만 때로는 외부의 환경이나 자원에 대한 정보를 필요로 한다. 예를 들어 정확한 출력을 위해 화면 크기를 알아야 할 경우도 있고 장식을 위해 외부에 정의된 예쁜 비트맵 리소스를 읽어야 하는 경우도 있다. 이런 정보들은 일반적으로 한번 읽어서 여러 번 사용할 수 있는 읽기 전용이기 때문에 객체별로 이 값을 일일이 조사하고 따로 유지할 필요가 없다.

 

: ReadOnlyInit

#include <Turboc.h>

 

class Shape

{

private:

     int ShapeType;

     RECT ShapeArea;

     COLORREF Color;

 

public:

     static int scrx,scry;

     static void GetScreenSize();

};

 

int Shape::scrx;

int Shape::scry;

 

void Shape::GetScreenSize()

{

     scrx=GetSystemMetrics(SM_CXSCREEN);

     scry=GetSystemMetrics(SM_CYSCREEN);

}

 

void main()

{

     Shape::GetScreenSize();

     Shape C,E,R;

     printf("화면 크기 = (%d,%d)\n",Shape::scrx,Shape::scry);

}

 

3) 모든 객체가 공유해야 하는 정보 관리 => 전역 용도의 값

중요한 계산을 하는 객체의 경우 계산에 필요한 기준값이 있을 수 있다. 예를 들어 환율이나 이자율 따위는 금융, 재무 처리에 상당히 중요한 기준값으로 작용하며 기준값에 따라 계산 결과가 달라진다. 이런 값들은 프로그램이 동작중일 때도 수시로 변할 수 있지만 일단 정해지면 모든 객체에 일관되게 적용된다.

 

: ShareInfo

#include <Turboc.h>

 

class Exchange

{

private:

     static double Rate;

 

public:

     static double GetRate() { return Rate; }

     static void SetRate(double aRate) { Rate=aRate; }

     double DollarToWon(double d) { return d*Rate; }

     double WonToDollar(double w) { return w/Rate; }

};

double Exchange::Rate;

 

void main()

{

     Exchange::SetRate(1200);

     Exchange A,B;

     printf("1달러는 %.0f원이다.\n",A.DollarToWon(1.0));

     B.SetRate(1150);

     printf("1달러는 %.0f원이다.\n",B.DollarToWon(1.0));

}

 

 

 10 .상수 변수


1) 상수 멤버

상수 멤버는 한번 값이 정해지면 변경될 수 없는 멤버이다. 클래스 전체에서 참조하는 중요한 상수가 있다면 이를 상수 멤버로 정의하여 클래스에 포함시킬 수 있다.

 

: ConstMember

#include <Turboc.h>

 

class MathCalc

{

private:

     const double pie;

 

public:

     MathCalc(double apie) : pie(apie) { }

     void DoCalc(double r) {

          printf("반지름 %.2f인 원의 둘레 = %.2f\n",r,r*2*pie);

     }

};

 

void main()

{

     MathCalc M(3.1416);

     M.DoCalc(5);

}

 

// 정적 변수와 연동 가능

 

 상수 멤버가 모든 객체에 대해 항상 같은 값을 가진다면 객체를 생성할 때마다 매번 초기화할 필요없이 정적 멤버로 선언한 후 딱 한번만 초기화할 수도 있다.

 

class MathCalc

{

private:

    static const double pie;

 

public:

    MathCalc() { }

     void DoCalc(double r) {

          printf("반지름 %.2f인 원의 둘레 = %.2f\n",r,r*2*pie);

     }

};

const double MathCalc::pie=3.1416;

 

void main()

{

    MathCalc M;

     M.DoCalc(5);

}

 

 

[IDEA] 객체별로 상수가 다른값을 가지게 하기

 

정적 상수 멤버는 클래스가 소유하기 때문에 객체별로 값을 따로 가질 수는 없다.

 열거형이나 매크로 상수도 마찬가지로 한 번 값이 정해지면 생성되는 모든 객체가 같은 값을 사용하는 수밖에 없다.

 상수가 객체별로 다른 값을 가져야 한다면 이때 쓸 수 있는 유일한 방법은 생성자의 초기화 리스트뿐이다.

 

 

: ConstMemberInit

class Enemy

{

private:

     const int Speed;

 

public:

     Enemy(int aSpeed) : Speed(aSpeed) { }

     void Move() {

          printf("%d의 속도로 움직인다.\n",Speed);

     }

};

 

void main()

{

     Enemy E1(10), E2(20);

     E1.Move();

     E2.Move();

}

 

=> Enemy 클래스는 게임의 적군을 표현하는 클래스인데 각 객체별로 고유한 속도를 가지되 한번 정해진 속도가 객체 내에서 불변이라면 Speed라는 상수 멤버를 선언한다. 그리고 객체가 생성될 때 생성자를 통해 딱 한번만 초기화한다.

 

 

2) 상수 멤버 함수

 

상수 멤버 함수는 멤버값을 변경할 수 없는 함수이다. 멤버값을 단순히 읽기만 한다면 이 함수는 객체의 상태를 바꾸지 않는다는 의미로 상수 멤버 함수로 지정하는 것이 좋다.

 

=> 어떤 멤버 함수가 값을 읽기만 하고 바꾸지는 않는다면 const를 붙여주는 것이 원칙이며 이 원칙대로 클래스를 작성해야 한다.

 

 

예)

class Some

{

private:

     int Value;

 

public:

     int SetValue(int aValue);        // 비상수 멤버 함수

    int GetValue() const;             // 상수 멤버 함수

};

 

: ConstFunc

#include <Turboc.h>

 

class Position

{

private:

     int x,y;

     char ch;

 

public:

     Position(int ax, int ay, char ach) { x=ax;y=ay;ch=ach; }

     void OutPosition() const { gotoxy(x, y);putch(ch); }

     void MoveTo(int ax, int ay) { x=ax; y=ay; }                                 // 상수로 지정하면 에러가 발생 한다.

};

 

void main()

{

     Position Here(1,2,'A');

 

     Here.MoveTo(20,5);

     Here.OutPosition();

 

     const Position There(3,4,'B');

     There.MoveTo(40,10);               

     There.OutPosition();

}

 

 

 [ 포인터와 상수 연동 ]

void func(const Position *Pos);

 

이 함수로 전달되는 Pos는 상수 지시 포인터이므로 Pos는 func 함수 안에서 상수 객체이다. 따라서 Pos 객체에 대해서는 상수 멤버 함수만 호출할 수 있다.

 

 [ 상수변수를 통한 같은 이름의 함수 호출 ]

 

함수의 상수성은 함수 원형의 일부로 포함된다. 그래서 똑같은 이름과 인수 목록이 같더라도 const가 있는 함수와 그렇지 않은 함수를 오버로딩할 수 있다. 즉, 다음 두 함수는 이름과 취하는 인수가 같더라도 다른 함수로 인식된다.

 

void func(int a, double b, char c) const;

void func(int a, double b, char c);

 

컴파일러는 상수 객체에 대해서는 위쪽의 상수 멤버 함수를 호출할 것이고 그렇지 않은 경우는 아래쪽의 비상수 멤버 함수를 호출할 것이다. 객체가 상수일 때와 그렇지 않을 때의 처리를 다르게 하고 싶다면 두 타입의 함수를 따로 제공하는 것도 가능하다

 

 

 

 

[ 예제 - 동적배열을 클래스로 변환 ]

 

동적 배열을 하나의 클래스안에 캡슐화해 보기로 하자. 구조적 프로그래밍 기법으로 작성한 동적 배열이 어떻게 클래스로 변환되는지 볼 수 있을 것이다

 

 

: DArray

#include <Turboc.h>

#include <iostream>

using namespace std;

 

#define ELETYPE int

class DArray

{

protected:

     ELETYPE *ar;

     unsigned size;

     unsigned num;

     unsigned growby;

 

public:

     DArray(unsigned asize=100, unsigned agrowby=10);

     ~DArray();

     void Insert(int idx, ELETYPE value);

     void Delete(int idx);

     void Append(ELETYPE value);

 

     ELETYPE GetAt(int idx) { return ar[idx]; }

     unsigned GetSize() { return size; }

     unsigned GetNum() { return num; }

     void SetAt(int idx, ELETYPE value) { ar[idx]=value; }

     void Dump(char *sMark);

};

 

DArray::DArray(unsigned asize, unsigned agrowby)

{

     size=asize;

     growby=agrowby;

     num=0;

     ar=(ELETYPE *)malloc(size*sizeof(ELETYPE));

}

 

DArray::~DArray()

{

     free(ar);

}

 

void DArray::Insert(int idx, ELETYPE value)

{

     unsigned need;

 

     need=num+1;

     if (need > size

블로그 이미지

요다할아범

,