C ++ 프라이빗 상속은 언제 사용해야합니까?


116

보호 된 상속과 달리 C ++ 개인 상속은 주류 C ++ 개발에 적용되었습니다. 그러나 여전히 좋은 용도를 찾지 못했습니다.

언제 사용 하시나요?

c++  oop 

답변:


60

답변 수락 후 참고 사항 : 이것은 완전한 답변이 아닙니다. 질문에 관심이있는 경우 여기 (개념적으로) 및 여기 (이론 및 실제) 와 같은 다른 답변을 읽으십시오 . 이것은 개인 상속으로 얻을 수있는 멋진 속임수 일뿐입니다. 화려 하지만 질문에 대한 답은 아닙니다.

C ++ FAQ (다른 사람의 의견에 링크 됨)에 표시된 개인 상속의 기본 사용법 외에도 개인 및 가상 상속의 조합을 사용 하여 클래스 를 봉인 하거나 (.NET 용어로) 클래스를 최종 (Java 용어로) 만들 수 있습니다. . 이것은 일반적인 사용은 아니지만 어쨌든 흥미로운 것을 발견했습니다.

class ClassSealer {
private:
   friend class Sealed;
   ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{ 
   // ...
};
class FailsToDerive : public Sealed
{
   // Cannot be instantiated
};

Sealed 는 인스턴스화 될 수 있습니다. ClassSealer 에서 파생되며 친구 인 것처럼 개인 생성자를 직접 호출 할 수 있습니다.

FailsToDeriveClassSealer 생성자를 직접 호출해야하므로 컴파일되지 않지만 (가상 상속 요구 사항) Sealed 클래스와이 경우에는 FailsToDerive 에서 비공개 이므로 컴파일 할 수 없습니다. 는 의 친구가 아닙니다 .


편집하다

이것은 CRTP를 사용하여 당시에는 일반적으로 만들 수 없다는 의견에서 언급되었습니다. C ++ 11 표준은 템플릿 인수와 친구가되도록 다른 구문을 제공하여 이러한 제한을 제거합니다.

template <typename T>
class Seal {
   friend T;          // not: friend class T!!!
   Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...

물론 C ++ 11은 final정확히이 목적을 위해 문맥 키워드를 제공하기 때문에 이것은 모두 문제입니다 .

class Sealed final // ...

그것은 훌륭한 기술입니다. 그것에 블로그 항목을 쓸 것입니다.

1
질문 : 가상 상속을 사용하지 않으면 FailsToDerive가 컴파일됩니다. 옳은?

4
+1. @Sasha : 맞습니다. 가장 많이 파생 된 클래스는 항상 가상 상속 된 모든 클래스의 생성자를 직접 호출하기 때문에 가상 상속이 필요합니다. 이는 일반 상속의 경우가 아닙니다.
j_random_hacker

5
봉인하려는 모든 클래스에 대해 사용자 정의 ClassSealer를 만들지 않고도 일반화 할 수 있습니다! 확인해보세요 : class ClassSealer {protected : ClassSealer () {}}; 그게 다야.

+1 Iraimbilanja, 매우 멋지다! BTW CRTP 사용에 대한 이전 의견 (현재 삭제됨)을 보았습니다. 실제로 작동해야한다고 생각합니다. 템플릿 친구에 대한 구문을 올바르게 가져 오는 것은 까다로울뿐입니다. 그러나 어떤 경우에 템플릿이 아닌 솔루션이 훨씬 더 굉장합니다 :)
j_random_hacker

138

나는 항상 그것을 사용합니다. 내 머릿속에서 몇 가지 예 :

  • 기본 클래스의 인터페이스 전부는 아니지만 일부를 노출하고 싶을 때. Liskov 대체 가능성 이 깨 졌기 때문에 공개 상속은 거짓말이 될 수 있지만 구성은 많은 전달 함수를 작성하는 것을 의미합니다.
  • 가상 소멸자없이 구체적인 클래스에서 파생하고 싶을 때. 공용 상속은 클라이언트가 기본 포인터를 통해 삭제하도록 초대하여 정의되지 않은 동작을 호출합니다.

일반적인 예는 STL 컨테이너에서 비공개로 파생하는 것입니다.

class MyVector : private vector<int>
{
public:
    // Using declarations expose the few functions my clients need 
    // without a load of forwarding functions. 
    using vector<int>::push_back;
    // etc...  
};
  • 어댑터 패턴을 구현할 때 Adapted 클래스에서 비공개로 상속하면 포함 된 인스턴스로 전달할 필요가 없습니다.
  • 개인 인터페이스를 구현합니다. 이것은 종종 관찰자 패턴과 함께 나타납니다. 일반적으로 내 Observer 클래스 인 MyClass는 자신을 구독합니다. 는 일부 Subject를 . 그런 다음 MyClass 만 MyClass-> Observer 변환을 수행하면됩니다. 나머지 시스템은 그것에 대해 알 필요가 없으므로 개인 상속이 표시됩니다.

4
@Krsna : 사실, 나는 그렇게 생각하지 않습니다. 여기에는 한 가지 이유가 있습니다. 게으름이 마지막과는 별개로 해결하기 더 까다로울 것입니다.
Matthieu M.

11
너무 게으름이 아닙니다 (좋은 의미로 의미하지 않는 한). 이를 통해 추가 작업없이 노출 된 함수의 새로운 오버로드를 생성 할 수 있습니다. C ++ 1 배에서 그들이 3 새 오버로드에 추가하면 push_back, MyVector무료로를 가져옵니다.
David Stone

@DavidStone, 템플릿 방법으로 할 수 없습니까?
Julien__

5
@Julien__ : 네, 쓸 template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }수도 있고 Base::f;. 당신이 개인 상속 그 기능과 유연성의 대부분을 원하고 있다면 using사항이 있습니다, 당신은 각 기능에 대한 그 몬스터를 (약 잊지 마세요 constvolatile과부하!).
데이비드 스톤

2
using 문 버전에없는 추가 이동 생성자를 여전히 호출하고 있기 때문에 대부분의 기능을 말합니다. 일반적으로이 기능은 최적화 될 것으로 예상되지만 이론적으로 함수는 값으로 이동 불가능한 유형을 반환 할 수 있습니다. 전달 함수 템플릿에는 추가 템플릿 인스턴스화 및 constexpr 깊이도 있습니다. 이로 인해 프로그램이 구현 제한에 도달 할 수 있습니다.
David Stone

31

개인 상속의 정식 사용은 "구현 된"관계입니다 (이 문구에 대한 Scott Meyers의 'Effective C ++'덕분에). 즉, 상속 클래스의 외부 인터페이스는 상속 된 클래스와 (표시되는) 관계가 없지만 내부적으로이를 사용하여 기능을 구현합니다.


6
이 경우에 사용되는 이유 중 하나를 언급 할 가치가있을 수 있습니다. 이렇게하면 빈 기본 클래스 최적화를 수행 할 수 있습니다. 이는 클래스가 기본 클래스가 아닌 멤버 인 경우에는 발생하지 않습니다.
jalf

2
주요 용도는 정책 제어 문자열 클래스 또는 압축 된 쌍에서와 같이 실제로 중요한 경우 공간 소비를 줄이는 것입니다. 실제로 boost :: compressed_pair는 보호 된 상속을 사용했습니다.
Johannes Schaub-litb 2007 년

jalf : 이봐, 난 몰랐어. 비공개 상속은 클래스의 보호 된 멤버에 액세스해야 할 때 주로 해킹으로 사용되었다고 생각했습니다. 그래도 컴포지션을 사용할 때 빈 개체가 공간을 차지하는 이유가 궁금합니다. 아마 보편적 주소 지정에 대한 ...

3
클래스를 복사 할 수 없도록 만드는 것도 편리합니다. 복사 할 수없는 빈 클래스에서 개인적으로 상속하기 만하면됩니다. 이제 선언하는 바쁜 작업을 수행 할 필요가 없지만 개인 복사 생성자 및 할당 연산자를 정의하지 않습니다. 마이어스도 이것에 대해 이야기합니다.
Michael Burr

나는이 질문이 실제로 보호 상속이 아닌 개인 상속에 관한 것임을 몰랐습니다. 네, 꽤 많은 응용 프로그램이 있다고 생각합니다. 그러나 보호 된 상속에 대한 많은 예를 생각할 수 없습니다 : / 거의 유용하지 않은 것처럼 보입니다.
Johannes Schaub-litb

23

private 상속의 한 가지 유용한 용도는 인터페이스를 구현하는 클래스가있을 때 다른 개체에 등록되는 경우입니다. 해당 인터페이스를 비공개로 설정하여 클래스 자체를 등록하고 등록 된 특정 개체 만 해당 함수를 사용할 수 있도록합니다.

예를 들면 :

class FooInterface
{
public:
    virtual void DoSomething() = 0;
};

class FooUser
{
public:
    bool RegisterFooInterface(FooInterface* aInterface);
};

class FooImplementer : private FooInterface
{
public:
    explicit FooImplementer(FooUser& aUser)
    {
        aUser.RegisterFooInterface(this);
    }
private:
    virtual void DoSomething() { ... }
};

따라서 FooUser 클래스는 FooInterface 인터페이스를 통해 FooImplementer의 private 메서드를 호출 할 수 있지만 다른 외부 클래스는 호출 할 수 없습니다. 이것은 인터페이스로 정의 된 특정 콜백을 처리하기위한 훌륭한 패턴입니다.


1
실제로 개인 상속은 개인 IS-A입니다.
curiousguy

18

C ++ FAQ Lite 의 중요한 섹션 은 다음과 같습니다.

개인 상속에 대한 합법적이고 장기적인 사용은 Wilma 클래스의 코드를 사용하는 Fred 클래스를 빌드하고 Wilma 클래스의 코드가 새 클래스 Fred에서 멤버 함수를 호출해야하는 경우입니다. 이 경우 Fred는 Wilma에서 비가 상을 호출하고 Wilma는 자체적으로 (일반적으로 순수 가상)을 호출하며 이는 Fred에 의해 재정의됩니다. 이것은 작곡과 관련하여 훨씬 더 어려울 것입니다.

확실하지 않은 경우 개인 상속보다 구성을 선호해야합니다.


4

다른 코드가 인터페이스 (상속하는 클래스 만)를 터치하지 않도록 상속하는 인터페이스 (즉, 추상 클래스)에 유용합니다.

[예제에서 편집 됨]

위에 링크 된 예를 사용 하십시오 . 에 대해 말하는 것

[...] 클래스 Wilma는 새 클래스 Fred에서 멤버 함수를 호출해야합니다.

이는 Wilma가 Fred가 특정 멤버 함수를 호출 할 수 있도록 요구하는 것입니다. 또는 Wilma가 인터페이스 라고 말하는 것입니다 . 따라서 예제에서 언급했듯이

사적 유산은 악이 아닙니다. 누군가가 당신의 코드를 깨뜨릴 수있는 무언가를 변경할 가능성을 증가시키기 때문에 유지하는 것은 더 비싸다.

인터페이스 요구 사항을 충족해야하는 프로그래머가 원하는 효과에 대한 의견이나 코드를 깨는 것입니다. 그리고 fredCallsWilma ()는 친구 만 보호되고 파생 클래스는 상속 된 인터페이스 (추상 클래스)를 만질 수 있으며 상속 클래스 만 (및 친구) 만질 수 있습니다.

[다른 예에서 편집 됨]

이 페이지 에서는 비공개 인터페이스에 대해 간략하게 설명합니다 (또 다른 각도에서).


정말 당신이 예를 게시 할 수 있습니다 ... 유용한 소리하지 않습니다

나는 당신이 어디로 가고 있는지 알고 있다고 생각합니다 ... 일반적인 사용 사례는 Wilma가 Fred에서 가상 함수를 호출 해야하는 일종의 유틸리티 클래스이지만 다른 클래스는 Fred가 용어로 구현되었음을 알 필요가 없다는 것입니다. 윌마. 권리?
j_random_hacker

예. 제 이해에 따르면 '인터페이스'라는 용어가 Java에서 더 일반적으로 사용된다는 점을 지적해야합니다. 처음 들었을 때 더 나은 이름이 주어질 수 있다고 생각했습니다. 이 예에서 우리는 일반적으로 단어를 생각하는 방식으로 아무도 인터페이스하지 않는 인터페이스를 가지고 있습니다.
바이어스

@Noos : 예, 대부분의 사람들이 Wilma가 Wilma와의 계약이 아니라 Fred 가 세계 에 공급하려는 인터페이스라는 의미로 "Wilma는 인터페이스입니다."라는 귀하의 진술이 약간 모호하다고 생각합니다 .
j_random_hacker

@j_ 그래서 인터페이스가 나쁜 이름이라고 생각합니다. 인터페이스라는 용어 는 생각하는 것처럼 세상에 의미가 있는 것이 아니라 기능을 보장하는 것입니다. 사실 저는 프로그램 디자인 수업에서 인터페이스라는 용어에 대해 논쟁을 벌였습니다. 그러나 우리는 주어진 것을 사용합니다 ...
bias

2

때로는 내부 클래스와 유사한 방식으로 컬렉션 구현이 노출 클래스의 상태에 액세스해야하는 다른 인터페이스에서 더 작은 인터페이스 (예 : 컬렉션)를 노출하려는 경우 개인 상속을 사용하는 것이 유용하다는 것을 알게되었습니다. 자바.

class BigClass;

struct SomeCollection
{
    iterator begin();
    iterator end();
};

class BigClass : private SomeCollection
{
    friend struct SomeCollection;
    SomeCollection &GetThings() { return *this; }
};

그런 다음 SomeCollection이 BigClass에 액세스해야하는 경우 static_cast<BigClass *>(this). 추가 데이터 멤버가 공간을 차지할 필요가 없습니다.


BigClass이 예제 에 is there 의 전방 선언이 필요하지 않습니다 . 나는 이것이 흥미 롭다고 생각하지만 내 얼굴에는 끔찍한 소리를 지른다.
Thomas Eding 2011 년

2

제한된 사용이 있지만 개인 상속에 대한 멋진 응용 프로그램을 찾았습니다.

해결해야 할 문제

다음 C API가 제공된다고 가정합니다.

#ifdef __cplusplus
extern "C" {
#endif

    typedef struct
    {
        /* raw owning pointer, it's C after all */
        char const * name;

        /* more variables that need resources
         * ...
         */
    } Widget;

    Widget const * loadWidget();

    void freeWidget(Widget const * widget);

#ifdef __cplusplus
} // end of extern "C"
#endif

이제 작업은 C ++를 사용하여이 API를 구현하는 것입니다.

C-ish 접근

물론 다음과 같이 C-ish 구현 스타일을 선택할 수 있습니다.

Widget const * loadWidget()
{
    auto result = std::make_unique<Widget>();
    result->name = strdup("The Widget name");
    // More similar assignments here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    free(result->name);
    // More similar manual freeing of resources
    delete widget;
}

그러나 몇 가지 단점이 있습니다.

  • 수동 리소스 (예 : 메모리) 관리
  • 설정하는 것은 쉽습니다 struct잘못
  • 리소스를 해제 할 때 리소스 해제를 잊어 버리기 쉽습니다. struct
  • C-ish입니다

C ++ 접근법

우리는 C ++를 사용할 수 있습니다. 그렇다면 C ++의 모든 기능을 사용하는 것은 어떨까요?

자동화 된 리소스 관리 소개

위의 문제는 기본적으로 모두 수동 리소스 관리와 관련이 있습니다. 떠오르는 해결책 은 각 변수에 대한 Widget파생 클래스에서 리소스 관리 인스턴스를 상속 하고 추가하는 것입니다 WidgetImpl.

class WidgetImpl : public Widget
{
public:
    // Added bonus, Widget's members get default initialized
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

private:
    std::string m_nameResource;
};

이는 다음과 같은 구현을 단순화합니다.

Widget const * loadWidget()
{
    auto result = std::make_unique<WidgetImpl>();
    result->setName("The Widget name");
    // More similar setters here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    // No virtual destructor in the base class, thus static_cast must be used
    delete static_cast<WidgetImpl const *>(widget);
}

이렇게 우리는 위의 모든 문제를 해결했습니다. 그러나 클라이언트는 여전히의 세터에 대해 잊을 수 WidgetImpl와 할당Widget 구성원에게 직접 있습니다.

개인 상속이 단계에 들어간다

Widget멤버 를 캡슐화하기 위해 개인 상속을 사용합니다. 안타깝게도 이제 두 클래스간에 캐스트하려면 두 개의 추가 함수가 필요합니다.

class WidgetImpl : private Widget
{
public:
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

    Widget const * toWidget() const
    {
        return static_cast<Widget const *>(this);
    }

    static void deleteWidget(Widget const * const widget)
    {
        delete static_cast<WidgetImpl const *>(widget);
    }

private:
    std::string m_nameResource;
};

따라서 다음과 같은 조정이 필요합니다.

Widget const * loadWidget()
{
    auto widgetImpl = std::make_unique<WidgetImpl>();
    widgetImpl->setName("The Widget name");
    // More similar setters here
    auto const result = widgetImpl->toWidget();
    widgetImpl.release();
    return result;
}

void freeWidget(Widget const * const widget)
{
    WidgetImpl::deleteWidget(widget);
}

이 솔루션은 모든 문제를 해결합니다. 수동 메모리 관리가 없으며 Widget멋지게 캡슐화되어 있으므로WidgetImpl 가 없으며 더 이상 공용 데이터 멤버가 됩니다. 구현을 올바르게 사용하기 쉽고 잘못 사용하기가 어렵습니다 (불가능합니까?).

코드 조각 은 Coliru 에서 컴파일 예제를 구성 합니다.


1

파생 클래스-코드를 재사용해야하고-기본 클래스를 변경할 수없고-잠금 상태에서 기본 멤버를 사용하여 해당 메서드를 보호하는 경우.

그런 다음 개인 상속을 사용해야합니다. 그렇지 않으면이 파생 클래스를 통해 내 보낸 잠금 해제 된 기본 메서드의 위험이 있습니다.


1

예를 들어 집계를 원하지만 집계 가능한 엔터티의 동작이 변경된 경우 (가상 함수 재정의) 집계 대신 사용할 수 있습니다 .

하지만 당신 말이 맞아요, 실제 세계에서 많은 예가 없습니다.


0

관계가 "is a"가 아닐 때 사용할 Private Inheritance를 사용할 수 있지만, New 클래스는 "기존 클래스의 관점에서 구현"하거나 기존 클래스와 "work like"할 수 있습니다.

"C ++ 코딩 표준 by Andrei Alexandrescu, Herb Sutter"의 예 :-두 클래스 Square 및 Rectangle에는 각각 높이와 너비를 설정하는 가상 기능이 있다고 가정합니다. 그러면 수정 가능한 Rectangle을 사용하는 코드는 SetWidth가 높이를 변경하지 않는다고 가정하지만 (Rectangle이 축소를 명시 적으로 문서화하는지 여부에 관계없이) Square :: SetWidth는 해당 계약 및 자체 직각도를 동시. 그러나 Square의 클라이언트가 예를 들어 Square의 영역이 너비의 제곱이라고 가정하거나 Rectangle을 유지하지 않는 다른 속성에 의존하는 경우에도 Rectangle은 Square에서 올바르게 상속 할 수 없습니다.

정사각형 "is-a"직사각형 (수학적)이지만 정사각형은 직사각형이 아닙니다 (동작 적으로). 결과적으로 "is-a"대신 "works-like-a"(또는 원하는 경우 "usable-as-a")라고 말하여 설명이 오해를 덜 받도록합니다.


0

클래스에는 불변성이 있습니다. 불변은 생성자에 의해 설정됩니다. 그러나 많은 상황에서 객체의 표현 상태를 보는 것이 유용합니다 (네트워크를 통해 전송하거나 파일에 저장할 수 있습니다-원하는 경우 DTO). REST는 AggregateType 측면에서 가장 잘 수행됩니다. const가 맞다면 특히 그렇습니다. 치다:

struct QuadraticEquationState {
   const double a;
   const double b;
   const double c;

   // named ctors so aggregate construction is available,
   // which is the default usage pattern
   // add your favourite ctors - throwing, try, cps
   static QuadraticEquationState read(std::istream& is);
   static std::optional<QuadraticEquationState> try_read(std::istream& is);

   template<typename Then, typename Else>
   static std::common_type<
             decltype(std::declval<Then>()(std::declval<QuadraticEquationState>()),
             decltype(std::declval<Else>()())>::type // this is just then(qes) or els(qes)
   if_read(std::istream& is, Then then, Else els);
};

// this works with QuadraticEquation as well by default
std::ostream& operator<<(std::ostream& os, const QuadraticEquationState& qes);

// no operator>> as we're const correct.
// we _might_ (not necessarily want) operator>> for optional<qes>
std::istream& operator>>(std::istream& is, std::optional<QuadraticEquationState>);

struct QuadraticEquationCache {
   mutable std::optional<double> determinant_cache;
   mutable std::optional<double> x1_cache;
   mutable std::optional<double> x2_cache;
   mutable std::optional<double> sum_of_x12_cache;
};

class QuadraticEquation : public QuadraticEquationState, // private if base is non-const
                          private QuadraticEquationCache {
public:
   QuadraticEquation(QuadraticEquationState); // in general, might throw
   QuadraticEquation(const double a, const double b, const double c);
   QuadraticEquation(const std::string& str);
   QuadraticEquation(const ExpressionTree& str); // might throw
}

이 시점에서 컨테이너에 캐시 컬렉션을 저장하고 생성시 조회 할 수 있습니다. 실제 처리가 있으면 편리합니다. 캐시는 QE의 일부입니다. QE에 정의 된 작업은 캐시를 부분적으로 재사용 할 수 있음을 의미 할 수 있습니다 (예 : c는 합계에 영향을주지 않음). 그러나 캐시가 없으면 찾아 볼 가치가 있습니다.

개인 상속은 거의 항상 멤버에 의해 모델링 될 수 있습니다 (필요한 경우 기본에 대한 참조 저장). 그런 식으로 모델링하는 것이 항상 가치가있는 것은 아닙니다. 때로는 상속이 가장 효율적인 표현입니다.


0

이 질문std::ostream 과 같이 약간의 변경 사항 이 필요한 경우 다음을 수행해야 할 수 있습니다.

  1. 다음에서 MyStreambuf파생되는 클래스 만들기std::streambuf 만들고 거기 변경 사항을 구현합니다.
  2. 의 인스턴스를 초기화 및 관리하고 해당 인스턴스에 대한 포인터를의 생성자에 전달 하는 클래스 MyOStream를 만듭니다.std::ostreamMyStreambufstd::ostream

첫 번째 아이디어는 MyStream인스턴스를 데이터 멤버로 MyOStream클래스 에 추가하는 것입니다 .

class MyOStream : public std::ostream
{
public:
    MyOStream()
        : std::basic_ostream{ &m_buf }
        , m_buf{}
    {}

private:
    MyStreambuf m_buf;
};

그러나 기본 클래스는 데이터 멤버보다 먼저 생성되므로 정의되지 않은 동작 인 아직 생성되지 않은 std::streambuf인스턴스에 대한 포인터를 전달합니다 std::ostream.

솔루션은 앞서 언급 한 질문에 대한 Ben의 답변 에서 제안됩니다. 먼저 스트림 버퍼에서 상속 한 다음 스트림에서 상속 한 다음 다음을 사용하여 스트림을 초기화합니다 this.

class MyOStream : public MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};

그러나 결과 클래스 std::streambuf는 일반적으로 원하지 않는 인스턴스 로도 사용될 수 있습니다 . 개인 상속으로 전환하면이 문제가 해결됩니다.

class MyOStream : private MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};

-1

C ++에 기능이 있다고해서 유용하거나 사용해야한다는 의미는 아닙니다.

나는 당신이 그것을 전혀 사용하지 말아야한다고 말하고 싶습니다.

어쨌든 그것을 사용한다면 기본적으로 캡슐화를 위반하고 응집력을 낮추는 것입니다. 한 클래스에 데이터를 넣고 다른 클래스에 데이터를 조작하는 메서드를 추가합니다.

다른 C ++ 기능과 마찬가지로 클래스를 봉인하는 것과 같은 부작용을 달성하는 데 사용할 수 있지만 (dribeas의 답변에서 언급했듯이) 이것은 좋은 기능이 아닙니다.


당신은 냉소적입니까? 내가 가진 건 -1! 어쨌든 나는 그것이 -100 표를 얻더라도 이것을 삭제하지 않을 것입니다
hasen

9
" 당신은 기본적으로 캡슐화를 위반하고 있습니다 "예를 들어 줄 수 있습니까?
curiousguy

1
한 클래스의 데이터와 다른 클래스의 동작은 유연성의 증가처럼 들립니다. 두 개 이상의 동작 클래스와 클라이언트가있을 수 있고 원하는 것을 충족하는 데 필요한 것을 선택할 수 있기 때문입니다
makar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.