왜 우 리 는 c + + 의 사전 성명 을 사용 해 야 합 니까?

8359 단어
전재 성명: (http://blog.csdn.net/fjb2080/archive/2010/04/27/5533514.aspx) 。 원작 자의 노고 에 감사 드 립 니 다.
본문
클래스 A 를 정의 합 니 다. 이 클래스 에 클래스 B 의 대상 b 를 사용 한 다음 에 클래스 B 를 정의 합 니 다. 그 안에 클래스 A 의 대상 a 도 포함 되 어 있 습 니 다. 이렇게 되 었 습 니 다.
//a.h  
#include "b.h"  
class A  
{  
....  
private:  
    B b;  
};  
//b.h  
#include "a.h"  
class B  
{  
....  
private:  
    A a;  
};

컴 파일 하 자마자 서로 포 함 된 문제 가 발생 했 습 니 다. 이때 누군가가 튀 어 나 와 서 이 문제 의 해결 방법 은 이렇게 할 수 있 습 니 다. a. h 파일 에서 클래스 B 를 설명 한 다음 에 B 의 지침 을 사용 할 수 있 습 니 다.
//a.h   
//#include "b.h"  
class B;   
class A   
{  
 ....   
private:  
 B b;   
};   
//b.h   
#include "a.h"   
class B  
{  
 ....   
private:  
 A a;   
};

그리고 문 제 는 해결 되 었 다.그러나 문제 가 왜 해결 되 었 는 지 아 는 사람 이 있 습 니까? 즉, 사전 성명 을 넣 고 왜 이런 문 제 를 해 결 했 는 지 하 는 것 입 니 다.다음은 제 가 이 사전 성명 을 검토 하 겠 습 니 다.이러한 사전 성명 은 많은 장점 이 있다.우리 가 선행 성명 을 사용 하 는 장점 중 하 나 는 위 에서 볼 수 있 듯 이, 우리 가 클래스 A 에서 클래스 B 의 선행 성명 을 사용 할 때, 클래스 B 를 수정 할 때, 클래스 B 를 다시 컴 파일 할 필요 가 없고, a. h 를 다시 컴 파일 할 필요 가 없다 는 것 이다. (물론, 클래스 B 를 진정 으로 사용 할 때, 반드시 b. h 를 포함해 야 한다.)또 다른 장점 은 클래스 A 의 크기 를 줄 이 는 것 이다. 위의 코드 가 나타 나 지 않 았 다 면 다음 과 같다.
class B;  
class A  
{  
    ....  
private:  
    B *b;  
....  
};  
//b.h  
class B  
{  
....  
private:  
    int a;  
    int b;  
    int c;  
}; 

우 리 는 위의 코드 를 보 았 는데, 클래스 B 의 크기 는 12 (32 비트 기기 에서) 였 다.
만약 에 우리 가 클래스 A 에 B 의 대상 을 포함한다 면 클래스 A 의 크기 는 12 (다른 구성원 변수 와 가상 함수 가 없다 고 가정) 입 니 다.클래스 B 의 포인터 * b 변 수 를 포함한다 면 클래스 A 의 크기 는 4 이기 때문에 클래스 A 의 크기 를 줄 일 수 있 습 니 다. 특히 STL 의 용기 에 포 함 된 클래스 의 대상 이 포인터 가 아 닌 클래스 의 대상 일 때 유용 합 니 다.선행 성명 에서 우 리 는 클래스 의 포인터 와 인용 만 사용 할 수 있 습 니 다. (인용 도 포인터 의 실현 에 있 기 때 문 입 니 다.)
요약: 1) 모듈 의 결합 을 낮 춘 다.클래스 의 실현 을 숨 겼 기 때문에 숨겨 진 클래스 는 원래 클래스 가 보이 지 않 는 것 과 같 습 니 다. 숨겨 진 클래스 를 수정 하고 원래 클래스 를 다시 컴 파일 할 필요 가 없습니다.2) 컴 파일 의존 도 를 낮 추고 컴 파일 속 도 를 높 인 다.포인터 의 크기 는 (32 비트) 또는 8 (64 비트) 이 고 X 는 변화 가 있 지만 포인터 의 크기 는 변 하지 않 으 며 파일 c. h 도 다시 컴 파일 할 필요 가 없습니다.3) 인터페이스 와 분 리 를 실현 하고 인터페이스의 안정성 을 향상 시킨다.1. 포인터 패 키 징 을 통 해 'new C' 또는 'C c1' 을 정의 할 때 컴 파일 러 가 생 성 한 코드 에 X 의 어떠한 정보 도 섞 이지 않 습 니 다.2. C 를 사용 할 때 C 의 인터페이스 (C 인터페이스 에서 작 동 하 는 종 류 는 사실 pImpl 구성원 이 가리 키 는 X 대상) 를 사용 하고 X 와 상 관 없 이 X 는 포인터 로 밀봉 되 어 철저하게 분리 된다.
그렇다면, 나 는 당신 에 게 한 가지 질문 을 하 겠 습 니 다. 왜 우리 가 사전에 성명 을 할 때, 유형의 지침 과 인용 만 사용 할 수 있 습 니까?
지침 이 고정 크기 이 고 임의의 유형 을 표시 할 수 있 기 때문에 80 점 을 줄 수 있다.왜 80 점 밖 에 안 돼? 아직 완전히 대답 하지 못 했 으 니까.
더 자세 한 답 을 원 하 시 면 다음 과 같은 종 류 를 보 겠 습 니 다.
class A  
{  
public:  
    A(int a):_a(a),_b(_a){} // _b is new add  
      
    int get_a() const {return _a;}  
    int get_b() const {return _b;} // new add  
private:  
    int _b; // new add  
    int _a;  
}; 

위 에서 정 의 된 이 클래스 A, 그 중b 변수 와 getb () 함 수 는 이 종류 에 새로 추 가 된 것 이다.그럼 내 가 묻 겠 다. 증가 하고 있다b 변수 와 getb () 멤버 함수 이후 이 클래스 에 무슨 변화 가 생 겼 는 지 생각해 보고 대답 하 세 요.
자, 우리 가 이 변 화 를 열거 합 시다.
1. 첫 번 째 변 화 는 당연히 증가b 변수 와 getb () 구성원 함수;2. 두 번 째 변 화 는 이런 종류의 크기 가 바 뀌 었 다 는 것 이다. 원래 4 였 는데 지금 은 8 이다.3. 세 번 째 변 화 는 멤버a 의 오프셋 주소 가 바 뀌 었 습 니 다. 원래 클래스 에 비해 0 이 었 는데 지금 은 4 입 니 다.4. 위의 변 화 는 모두 우리 의 현식 적 이 고 보 이 는 변화 이다.또 하나의 숨겨 진 변화 가 있다. 무엇 인지 생각해 보 자...
이 숨겨 진 변 화 는 클래스 A 의 기본 구조 함수 와 기본 복사 구조 함수 가 바 뀌 었 습 니 다.위의 변 화 를 통 해 알 수 있 듯 이 클래스 A 의 구성원 변수 나 구성원 함 수 를 호출 하 는 모든 행 위 는 바 뀌 어야 하기 때문에 우리 의 a. h 는 다시 컴 파일 해 야 합 니 다.만약 우리 의 b. h 가 이렇다 면:
//b.h  
#include "a.h"  
class B  
{  
...  
private:  
    A a;  
}; 

그럼 우리 b. h 도 다시 컴 파일 해 야 돼 요.만약 그렇다면:
class A;  
class B  
{  
...  
private:  
    A *a;  
}; 

그럼 우리 b. h 는 다시 컴 파일 할 필요 가 없습니다.우리 처럼 선행 성명 류 A: class A;클래스 B 에서 클래스 A 의 크기 나 구성원 을 알 아야 하 는 동작 이 실행 되 지 않 으 면 이러한 불완전한 성명 은 성명 이 A 를 가리 키 는 지침 과 인용 을 허용 합 니 다.이전 코드 에 있 는 문구 A;A 의 크기 를 알 아야 합 니 다. 그렇지 않 으 면 클래스 B 에 메모리 크기 를 할당 하면 알 수 없 기 때문에 불완전한 사전 성명 은 안 됩 니 다. a. h 를 포함 하여 클래스 A 의 크기 를 얻 는 동시에 클래스 B 도 다시 컴 파일 해 야 합 니 다.다시 앞의 문제 로 돌아 가 사전 성명 만 허용 하 는 성명 을 사용 하 는 것 은 포인터 나 인용 의 한 가지 이 유 는 이 성명 이 클래스 A 의 크기 나 구성원 의 작업 을 알 아야 하기 때문에 포인터 나 인용 은 클래스 A 의 크기 나 구성원 의 작업 을 알 아야 하기 때 문 입 니 다.
2. 사용 총화
이 글 은 Exceptional C + + (Hurb 99) 책 에서 제4 장 Compiler Firewalls and the Pimpl Idom (컴 파일 러 방화벽 과 Pimpl 관용 법) 의 시사 점 을 많이 받 았 다. 이 장 은 컴 파일 할 때 의존 하 는 의미 와 관용 법 을 줄 이 는 것 을 다 루 었 다. 사실은 가장 자주 사용 되 고 부작용 이 없 는 것 은 헤드 파일 을 포함 한 사전 성명 을 사용 하 는 것 이다.
Item 26 의 Guideline - "Never \ # include a header when a forward declaration will suffice"
여기 서 나 는 헤드 파일 을 포함 한 여러 가지 상황 을 대체 하고 예제 코드 를 제시 할 수 있 는 사전 성명 을 정리 했다.
우선, 우 리 는 왜 헤더 파일 을 포함해 야 합 니까?질문 에 대한 대답 은 매우 간단 하 다. 일반적으로 우 리 는 특정한 유형의 정 의 를 얻어 야 한다 (definition).그렇다면 다음 문 제 는 어떤 상황 에서 우 리 는 유형의 정의 가 필요 합 니까? 어떤 상황 에서 우 리 는 성명 만 하면 충분 합 니까?질문 에 대한 대답 은 이 유형의 크기 를 알 아야 하거나 함수 서명 을 알 아야 할 때 정 의 를 받 아야 한 다 는 것 이다.
만약 에 우리 가 유형 A 와 유형 C 가 있다 고 가정 하면 어떤 상황 에서 A 에 C 의 정의 가 필요 합 니까?
1.A   C
2.A      C     
3.A      C        
4.A      C        
5.A      std::list     
6.A     ,               C
7.A     ,               C,    C     ,       
8.A     ,               C(    C  ,C      C     ),            C   ,      A     
9.C A          
10.C A          

1. 방법 이 없습니다. C 의 정 의 를 받 아야 합 니 다. 왜냐하면 우 리 는 C 의 구성원 변수, 구성원 함 수 를 알 아야 하기 때 문 입 니 다.2. C 의 정의 가 필요 합 니 다. 왜냐하면 우 리 는 C 의 크기 를 알 고 A 의 크기 를 확인 해 야 하기 때 문 입 니 다. 그러나 Pimpl 관용 법 으로 이 점 을 개선 할 수 있 습 니 다. 자세 한 내용 은 Hurb 의 Exceptional C + + 를 보십시오.3.4. 필요 하지 않 습 니 다. 사전 성명 을 하면 됩 니 다. 사실 3 과 4 는 똑 같 습 니 다. 인용 은 물리 적 으로 도 지침 입 니 다. 그 크기 는 플랫폼 에 따라 32 자리 일 수도 있 고 64 자리 일 수도 있 습 니 다. 어쨌든 우 리 는 C 의 정 의 를 알 필요 가 없 으 면 이 구성원 변수의 크기 를 확인 할 수 있 습 니 다.5. 필요 없어 요. 구식 컴 파일 러 가 필요 할 수도 있어 요.표준 라 이브 러 리 에 있 는 용 기 는 list, vector, map 와 같 습 니 다. list, vector, map 형식의 구성원 변 수 를 포함 할 때 C 의 정의 가 필요 하지 않 습 니 다.내부 에서 도 C 의 지침 을 구성원 변수 로 사용 하기 때문에 크기 는 처음부터 고정 되 어 있 고 모델 매개 변수 에 따라 달라 지지 않 습 니 다.6. 필요 없어 요. 우리 가 C 를 사용 하지 않 았 다 면.7. 필요 합 니 다. 호출 함수 의 서명 을 알 아야 합 니 다.8, 8 의 상황 은 비교적 복잡 하 므 로 코드 를 직접 보면 비교적 명확 할 것 이다.
      C& doToC(C&);        
      C& doToC2(C& c) {return doToC(c);};

위의 코드 를 보면 A 의 한 구성원 함수 doToC 2 는 다른 구성원 함수 doToC 를 호출 했 습 니 다. 그러나 doToC 2 든 doToC 든 그들의 매개 변수 와 반환 유형 은 모두 C 의 인용 (포인터 로 바 꾸 면 상황 도 마찬가지 입 니 다) 입 니 다. 인용 한 할당 은 포인터 의 할당 과 같 습 니 다. 성형 의 할당 일 뿐 입 니 다.그래서 여 기 는 C 의 크기 를 알 필요 도 없고 C 를 호출 하 는 함수 도 없 으 며 실제로 C 의 정의 가 필요 하지 않 습 니 다.
그러나 우 리 는 그 중의 C & 를 C 로 마음대로 바 꾸 었 다. 예 를 들 어 아래 의 몇 가지 예 와 같다.
1.C& doToC(C&);           
   C& doToC2(C c) {return doToC(c);};                                
2.C& doToC(C);               
   C& doToC2(C& c) {return doToC(c);};                
3.C doToC(C&);                
   C& doToC2(C& c) {return doToC(c);};                
4.C& doToC(C&);                
   C doToC2(C& c) {return doToC(c);};

어느 것 이 든 사실은 모두 암시 적 으로 복사 구조 함수 의 호출 을 포함 하고 있다. 예 를 들 어 1 중의 매개 변수 c 는 복사 구조 함수 로 생 성 되 고 3 중의 doToC 의 반환 값 은 복사 구조 함수 로 생 성 된 익명 대상 이다.우 리 는 C 의 복사 구조 함 수 를 호출 했 기 때문에 이상 은 그런 상황 에서 도 C 의 정 의 를 알 아야 한다.
9 와 10 이 똑 같 습 니 다. 우 리 는 C 의 정 의 를 알 필요 가 없습니다. 다만 10 의 경우 선행 성명 의 문법 이 약간 복잡 할 것 입 니 다.마지막 으로 완전한 예 를 들 어 우 리 는 두 개의 서로 다른 이름 공간 에 있 는 유형 A 와 C 를 볼 수 있 습 니 다. A 는 C 를 직접 포함 하 는 헤더 파일 을 어떻게 사용 하 는 지 알 수 있 습 니 다.
#pragma once  
  
#include   
#include   
#include   
#include   
  
    //               
namespace test1  
{  
          class C;  
}  
  
  
  
namespace test2  
{     
       // using           
    using test1::C;  
      
    class A   
    {  
    public:  
                C   useC(C);  
            C& doToC(C&);  
            C& doToC2(C& c) {return doToC(c);};  
                           
    private:  
            std::list    _list;  
            std::vector  _vector;  
            std::map  _map;  
            C*              _pc;  
            C&              _rc;  
      
    };  
} 
#ifndef C_H  
#define C_H  
#include   
  
namespace test1  
{  
            
    class C  
    {  
    public:  
           void print() {std::cout<

좋은 웹페이지 즐겨찾기