왜 C ++에서 중첩 클래스를 사용합니까? Center-Nested Classes 와

누군가가 중첩 클래스를 이해하고 사용하는 데 도움이되는 멋진 자료를 알려 주시겠습니까? Programming Principles와 같은 자료와이 IBM Knowledge Center-Nested Classes 와 같은 자료가 있습니다.

그러나 나는 여전히 그들의 목적을 이해하는 데 어려움을 겪고 있습니다. 누군가 제발 도와주세요?



답변

중첩 클래스는 구현 세부 정보를 숨기는 데 유용합니다.

명부:

class List
{
    public:
        List(): head(nullptr), tail(nullptr) {}
    private:
        class Node
        {
              public:
                  int   data;
                  Node* next;
                  Node* prev;
        };
    private:
        Node*     head;
        Node*     tail;
};

다른 사람들이 클래스를 사용하기로 결정할 수 있으므로 Node를 노출하고 싶지 않으며 노출 된 것이 공개 API의 일부이므로 영구적 으로 유지 관리해야하기 때문에 클래스를 업데이트하지 못하게 됩니다 . 클래스를 비공개로 설정하면 구현을 숨길뿐만 아니라 이것이 내 것이라고 말하고 언제든지 사용할 수 없도록 변경할 수 있습니다.

std::list또는 std::map그들은 모두 숨겨진 클래스를 포함 (또는합니까?). 요점은 그럴 수도 있고 아닐 수도 있지만 구현이 비공개이며 숨겨져 있기 때문에 STL 빌더는 코드 사용 방법에 영향을주지 않고 STL 주위에 많은 오래된 수하물을 남기지 않고 코드를 업데이트 할 수있었습니다. 안에 숨겨져있는 Node 클래스를 사용하기로 결정한 일부 바보와의 호환성을 유지하기 위해 list.


답변

중첩 클래스는 일반 클래스와 비슷하지만

  • (클래스 정의 내부의 모든 정의와 마찬가지로) 추가 액세스 제한이 있습니다.
  • 그들은 주어진 네임 스페이스를 오염시키지 않는 글로벌 네임 스페이스를 예. 클래스 B가 클래스 A에 너무 깊게 연결되어 있다고 생각하지만 A와 B의 객체가 반드시 관련이있는 것은 아니라면 A 클래스 범위 지정을 통해서만 클래스 B에 액세스 할 수 있기를 원할 수 있습니다 (A라고 함). ::수업).

몇 가지 예 :

클래스를 공개적으로 중첩하여 관련 클래스의 범위에 배치


class의 SomeSpecificCollection객체를 집계 하는 클래스를 원한다고 가정하십시오 Element. 그러면 다음 중 하나를 수행 할 수 있습니다.

  1. 두 개의 클래스를 선언합니다 : SomeSpecificCollection그리고 Element– “Element”라는 이름이 이름 충돌을 일으킬 정도로 일반적이기 때문에 나쁩니다.

  2. 네임 스페이스를 소개 someSpecificCollection하고 클래스를 선언 someSpecificCollection::Collection하고 someSpecificCollection::Element. 이름 충돌의 위험은 없지만 더 자세한 정보를 얻을 수 있습니까?

  3. 이 개 글로벌 클래스 선언 SomeSpecificCollectionSomeSpecificCollectionElement– 사소한 단점이 있지만, 아마 OK이다.

  4. 전역 클래스 SomeSpecificCollection와 클래스 Element를 중첩 클래스로 선언하십시오 . 그때:

    • Element가 전역 네임 스페이스에 없기 때문에 이름 충돌의 위험이 없습니다.
    • SomeSpecificCollection당신 을 구현할 때 just Element, 그리고 다른 곳 SomeSpecificCollection::Element은-3과 같지만 +와 동일하지만 더 명확합니다.
    • “컬렉션의 특정 요소”가 아니라 “특정 컬렉션의 요소”라는 것이 단순 해집니다.
    • 그 볼 SomeSpecificCollection도 클래스입니다.

제 생각에는 마지막 변형이 가장 직관적이며 따라서 최고의 디자인입니다.

스트레스를 드리겠습니다-더 자세한 이름을 가진 두 개의 글로벌 클래스를 만드는 것과 큰 차이는 없습니다. 아주 작은 세부 사항이지만 코드를 더 명확하게 만듭니다.

클래스 범위 내에서 다른 범위 소개


이것은 typedef 또는 enum을 도입 할 때 특히 유용합니다. 여기에 코드 예제를 게시하겠습니다.

class Product {
public:
    enum ProductType {
        FANCY, AWESOME, USEFUL
    };
    enum ProductBoxType {
        BOX, BAG, CRATE
    };
    Product(ProductType t, ProductBoxType b, String name);

    // the rest of the class: fields, methods
};

그런 다음 다음을 호출합니다.

Product p(Product::FANCY, Product::BOX);

그러나에 대한 코드 완성 제안을 살펴보면 Product::가능한 모든 열거 형 값 (BOX, FANCY, CRATE)이 나열되어 있고 여기에서 실수하기 쉽습니다 (C ++ 0x의 강력한 형식의 열거 형은 해결하지만 결코 신경 쓰지 않습니다) ).

그러나 중첩 클래스를 사용하여 열거 형에 대한 추가 범위를 도입하면 다음과 같이 보일 수 있습니다.

class Product {
public:
    struct ProductType {
        enum Enum { FANCY, AWESOME, USEFUL };
    };
    struct ProductBoxType {
        enum Enum { BOX, BAG, CRATE };
    };
    Product(ProductType::Enum t, ProductBoxType::Enum b, String name);

    // the rest of the class: fields, methods
};

그런 다음 호출은 다음과 같습니다.

Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);

그런 다음 Product::ProductType::IDE 에 입력 하면 원하는 범위에서 열거 형을 얻을 수 있습니다. 또한 실수 할 위험이 줄어 듭니다.

물론 이것은 작은 클래스에는 필요하지 않지만 열거 형이 많으면 클라이언트 프로그래머가 더 쉽게 만들 수 있습니다.

같은 방법으로, 필요한 경우 템플릿에 많은 타입 정의를 “구성”할 수 있습니다. 때로는 유용한 패턴입니다.

PIMPL 관용구


PIMPL (Pointer to IMPLementation의 줄임말)은 헤더에서 클래스의 구현 세부 사항을 제거하는 데 유용한 관용구입니다. 이렇게하면 헤더의 “구현”부분이 변경 될 때마다 클래스 헤더에 따라 클래스를 다시 컴파일 할 필요가 줄어 듭니다.

일반적으로 중첩 클래스를 사용하여 구현됩니다.

Xh :

class X {
public:
    X();
    virtual ~X();
    void publicInterface();
    void publicInterface2();
private:
    struct Impl;
    std::unique_ptr<Impl> impl;
}

X.cpp :

#include "X.h"
#include <windows.h>

struct X::Impl {
    HWND hWnd; // this field is a part of the class, but no need to include windows.h in header
    // all private fields, methods go here

    void privateMethod(HWND wnd);
    void privateMethod();
};

X::X() : impl(new Impl()) {
    // ...
}

// and the rest of definitions go here

이것은 전체 클래스 정의가 무겁거나 못생긴 헤더 파일 (WinAPI 사용)이있는 일부 외부 라이브러리의 유형 정의가 필요한 경우에 특히 유용합니다. PIMPL을 사용하는 경우 모든 WinAPI 관련 기능 만에 .cpp포함시킬 수 있으며에 포함시킬 수 없습니다 .h.


답변

중첩 클래스를 많이 사용하지 않지만 지금은 사용합니다. 특히 일종의 데이터 유형을 정의 할 때 해당 데이터 유형에 맞게 설계된 STL 펑터를 정의하려고합니다.

예를 들어, FieldID 번호, 유형 코드 및 필드 이름이 있는 일반 클래스를 고려하십시오 . ID 번호 또는 이름 vector으로 이들 Field중 하나 를 검색 하려면 functor를 구성하여 그렇게 할 수 있습니다.

class Field
{
public:
  unsigned id_;
  string name_;
  unsigned type_;

  class match : public std::unary_function<bool, Field>
  {
  public:
    match(const string& name) : name_(name), has_name_(true) {};
    match(unsigned id) : id_(id), has_id_(true) {};
    bool operator()(const Field& rhs) const
    {
      bool ret = true;
      if( ret && has_id_ ) ret = id_ == rhs.id_;
      if( ret && has_name_ ) ret = name_ == rhs.name_;
      return ret;
    };
    private:
      unsigned id_;
      bool has_id_;
      string name_;
      bool has_name_;
  };
};

그런 다음 이들을 검색해야하는 코드 는 클래스 자체 내에서 범위를 Field사용할 수 있습니다 .matchField

vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));


답변

중첩 클래스를 사용하여 빌더 패턴을 구현할 수 있습니다 . 특히 C ++에서는 개인적으로 의미가 더 깨끗합니다. 예를 들면 다음과 같습니다.

class Product{
    public:
        class Builder;
}
class Product::Builder {
    // Builder Implementation
}

오히려 :

class Product {}
class ProductBuilder {}


답변