C ++에서 상속 된 우정을 허용하지 않는 이유는 무엇입니까?


95

C ++에서 우정이 적어도 선택적으로 상속되지 않는 이유는 무엇입니까? 나는 명백한 이유로 인해 전이성과 재 귀성이 금지된다는 것을 이해하지만 (단순한 FAQ 인용문 답변을 언급하기 위해서만이 말을합니다), virtual friend class Foo;퍼즐 라인을 따라 무언가가 부족하다는 것은 저 를 괴롭 힙니다. 이 결정의 역사적 배경을 아는 사람이 있습니까? 우정은 정말 몇 가지 모호한 존경할만한 용도로 나아간 제한된 해킹 이었습니까?

명확히하기 위해 편집 : A의 자식이 B와 B와 자식 모두에게 노출되는 것이 아니라 다음 시나리오에 대해 이야기 하고 있습니다. 친구 기능의 재정의 등에 대한 액세스 권한을 선택적으로 부여하는 것도 상상할 수 있습니다.

class A {
  int x;
  friend class B;
};

class B {
  // OK as per friend declaration above.
  void foo(A& a, int n) { a.x = n; }
};

class D : public B { /* can't get in A w/o 'friend class D' declaration. */ };

허용 대답은 :로키 상태 , 효과는 매우 엄격한 없다, 더 많거나 적은 friended했다 기본 클래스에서 보호 프록시 기능을함으로써 시뮬레이션 할 수 있습니다 필요 클래스 또는 가상 메서드 계층 구조에 우정을 부여하는가. 나는 상용구 프록시 (친구 기반이 효과적으로되는)의 필요성을 싫어하지만, 이것이 대부분의 시간 동안 오용 될 가능성이 더 높은 언어 메커니즘보다 선호되는 것으로 간주되었다고 생각합니다. 나는 아마도 Stroupstrup의 The Design and Evolution of C ++를 구입하고 읽을 때라고 생각합니다. 이런 유형의 질문에 대한 더 나은 통찰력을 얻기 위해 여기에서 충분한 사람들이 추천하는 것을 보았습니다.

답변:


94

내가 쓸 수 있기 때문에 Foo그 친구 Bar(따라서 신뢰 관계가 있습니다).

그러나 나는 파생 된 클래스를 작성하는 사람들을 신뢰 Bar합니까?
별로. 따라서 그들은 우정을 상속해서는 안됩니다.

클래스의 내부 표현을 변경하려면 해당 표현에 의존하는 모든 항목을 수정해야합니다. 따라서 클래스의 모든 멤버와 클래스의 모든 친구는 수정이 필요합니다.

내부 표현 따라서 만약 Foo다음 수정은 Bar(친구 단단히 결합 있기 때문에 수정해야 BarFoo). 우정이 상속되면에서 파생 된 모든 클래스 Bar도 밀접하게 바인딩 Foo되므로 Foo의 내부 표현이 변경 되면 수정이 필요합니다 . 하지만 파생 된 유형에 대한 지식이 없습니다 (나도 마찬가지입니다. 다른 회사에서 개발할 수도 있음). 따라서 Foo에서 파생 된 모든 클래스를 수정할 수 없기 때문에 코드베이스에 주요 변경 사항이 도입되므로 변경할 수 없습니다 Bar.

따라서 우정이 물려받은 경우 실수로 클래스를 수정하는 기능에 제한이 있습니다. 이는 기본적으로 공용 API의 개념을 쓸모 없게 렌더링하므로 바람직하지 않습니다.

참고 :의 자식은 을 사용하여 Bar액세스 할 수 있습니다 . 메서드를 protected로 설정하면 됩니다. 그러면의 자식은 부모 클래스를 통해 호출 하여 에 액세스 할 수 있습니다 .FooBarBarBarFoo

이것이 당신이 원하는 것입니까?

class A
{
    int x;
    friend class B;
};

class B
{
    protected:
       // Now children of B can access foo
       void foo(A& a, int n) { a.x = n; }
};

class D : public B
{
    public:
        foo(A& a, int n)
        {
            B::foo(a, n + 5);
        }
};

4
빙고. 클래스의 내부를 변경하여 발생할 수있는 피해를 제한하는 것이 전부입니다.
j_random_hacker 2010-08-25

솔직히 제가 생각하고있는 사례는 Attorney-Client 패턴입니다. 중간 클래스는 래퍼 메서드를 기본 액세스 제한 클래스에 제공함으로써 외부 클래스에 대한 제한된 인터페이스 역할을합니다. 정확한 클래스가 아닌 다른 클래스의 모든 자식이 인터페이스를 사용할 수 있다고 말하는 것이 현재 시스템보다 훨씬 유용합니다.
제프

@Jeff : 클래스의 모든 자식에게 내부 표현을 노출하면 코드를 변경할 수 없게됩니다 (실제로 내부 멤버에 액세스하려는 사람이 실제로 Bar가 아니더라도 Bar에서 상속하면되므로 캡슐화가 깨집니다. ).
Martin York

@Martin : 맞습니다.이 체계에서 friended base는 friending 클래스에 도달하는 데 사용될 수 있습니다. 이것은 많은 경우 (대부분은 아니지만) 간단한 캡슐화 위반 일 수 있습니다. 그러나 friended base가 추상 클래스 인 상황에서는 파생 된 모든 클래스가 자체 상호 인터페이스를 구현하도록 강제됩니다. 이 시나리오에서 '사기꾼'클래스가 캡슐화를 깨는 것으로 간주되는지 아니면 자신의 주장 된 역할을 제대로 수행하려고하지 않으면 인터페이스 계약을 위반하는 것으로 간주 될지 확실하지 않습니다.
제프

@Martin : 맞아요, 그것은 제가 원하고 때로는 실제로 이미 사용하고있는 효과입니다. A는 실제로 제한된 액세스 클래스 Z에 대한 상호 친구 인터페이스입니다. 일반적인 Attorney-Client Idiom에 대한 일반적인 불만은 인터페이스 클래스 인 것 같습니다. A는 Z로 래퍼를 호출해야하며, 하위 클래스에 대한 액세스를 확장하기 위해 A의 상용구는 B와 같은 모든 기본 클래스에서 기본적으로 복제되어야합니다. 인터페이스는 일반적으로 모듈이 제공하려는 기능을 표현하고 다른 모듈의 기능을 표현하지 않습니다. 자신을 사용하고 싶어합니다.
제프

48

C ++에서 우정이 적어도 선택적으로 상속되지 않는 이유는 무엇입니까?

첫 번째 질문에 대한 답은 "아버지의 친구가 귀하의 개인 정보에 액세스 할 수 있습니까?"라는 질문에 있다고 생각합니다.


36
공정하게 말하면, 그 질문은 당신의 아버지에 대해 불안한 사람들을 불러 일으 킵니다. . .
iheanyi

3
이 답변의 요점은 무엇입니까? 최상의 가능성이 빛하지만 반신 반의하는 마음 주석
DeveloperChris

11

friended 클래스는 접근 자 함수를 통해 친구를 노출 한 다음이를 통해 액세스 권한을 부여 할 수 있습니다.

class stingy {
    int pennies;
    friend class hot_girl;
};

class hot_girl {
public:
    stingy *bf;

    int &get_cash( stingy &x = *bf ) { return x.pennies; }
};

class moocher {
public: // moocher can access stingy's pennies despite not being a friend
    int &get_cash( hot_girl &x ) { return x.get_cash(); }
};

이를 통해 선택적 전이성보다 더 미세한 제어가 가능합니다. 예를 들어, 런타임 제한 액세스 프로토콜을 적용하거나 시행 할 get_cash수 있습니다 protected.


@Hector : 리팩토링 투표! ;)
Alexander Shukaev

8

C ++ 표준, 섹션 11.4 / 8

우정은 상속되거나 전이되지 않습니다.

우정이 상속되면 친구가 아닌 클래스가 갑자기 클래스 내부에 액세스하여 캡슐화를 위반하게됩니다.


2
Q "친구"A, B는 A에서 파생 된 것입니다. B가 A로부터 우정을 물려 받았다면 B는 A 유형이기 때문에 기술적으로 Q의 개인 정보에 액세스 할 수있는 A입니다. 따라서 이것은 실제적인 이유가있는 질문에 대한 답이 아닙니다.
mdenton8

2

불필요하기 때문입니다.

friend키워드 사용 자체가 의심 스럽습니다. 커플 링 측면에서 보면 최악의 관계입니다 (상속 및 구성보다 훨씬 앞선).

클래스의 내부를 변경하면이 클래스의 친구들에게 영향을 미칠 위험이 있습니다. 정말 알 수없는 수의 친구를 원하십니까? 그들로부터 상속받은 사람들도 친구가 될 수 있고 매번 클라이언트 코드를 깨뜨릴 위험에 처할 수 있다면 그것들을 나열 할 수 없을 것입니다. 확실히 이것은 바람직하지 않습니다.

나는 숙제 / 애완 동물 프로젝트에 대한 의존성이 종종 멀리 떨어진 고려 사항이라는 것을 자유롭게 인정합니다. 소규모 프로젝트에서는 중요하지 않습니다. 그러나 여러 사람이 동일한 프로젝트에서 작업하고 이것이 수십만 개의 라인으로 늘어나 자마자 변경의 영향을 제한해야합니다.

이것은 매우 간단한 규칙을 가져옵니다.

클래스의 내부 변경은 클래스 자체에만 영향을 미칩니다.

물론 친구에게 영향을 미칠 수 있지만 여기에는 두 가지 경우가 있습니다.

  • 친구 무료 기능 : 어쨌든 멤버 기능이 더 많을 것입니다 (나는 std::ostream& operator<<(...) 순전히 언어 규칙의 우연히 멤버가 아닌 같습니다.
  • 친구 수업? 실제 수업에는 친구 수업이 필요하지 않습니다.

간단한 방법을 사용하는 것이 좋습니다.

class Example;

class ExampleKey { friend class Example; ExampleKey(); };

class Restricted
{
public:
  void forExampleOnly(int,int,ExampleKey const&);
};

이 간단한 Key패턴을 사용하면 실제로 내부에 대한 액세스 권한을 부여하지 않고 친구를 선언 할 수 있으므로 변경 사항으로부터 격리 할 수 ​​있습니다. 또한 필요한 경우이 친구가 수탁자 (예 : 자녀)에게 열쇠를 빌려줄 수 있습니다.


0

추측 : 클래스가 다른 클래스 / 함수를 친구로 선언하면 두 번째 항목이 첫 번째 항목에 대한 권한있는 액세스가 필요하기 때문입니다. 두 번째 엔티티에 첫 번째에서 파생 된 임의의 수의 클래스에 대한 권한있는 액세스 권한을 부여하는 데 어떤 용도가 있습니까?


2
클래스 A가 B와 그 자손에게 우정을 부여하고 싶다면 추가 된 각 하위 클래스에 대한 인터페이스를 업데이트하거나 B가 통과 상용구를 작성하도록 강요하는 것을 피할 수 있습니다. 이것은 처음에 우정 포인트의 절반입니다.
Jeff

@Jeff : 아, 그렇다면 의도 한 의미를 오해했습니다. 나는 당신이 B그로부터 상속받은 모든 클래스에 접근 할 수 있다는 것을 의미한다고 가정 했습니다 A.
Oliver Charlesworth

0

파생 클래스는 기본의 '구성원'인 무언가 만 상속 할 수 있습니다. 친구 선언은 친구가되는 클래스의 구성원 이 아닙니다 .

$ 11.4 / 1- "... 친구의 이름은 클래스 범위에 포함되지 않으며, 다른 클래스의 멤버가 아닌 경우 멤버 액세스 연산자 (5.2.5)로 친구가 호출되지 않습니다."

$ 11.4- "또한 friend 클래스의 base-clause는 멤버 선언의 일부가 아니기 때문에 friend 클래스의 base-clause는 우정을 부여하는 클래스에서 private 및 protected 멤버의 이름에 액세스 할 수 없습니다."

그리고 더

$ 10.3 / 7- "[참고 : 가상 지정자는 멤버쉽을 의미하므로 가상 함수는 비 멤버 (7.1.2) 함수가 될 수 없습니다. 가상 함수 호출은 특정 객체에 의존하기 때문에 가상 함수는 정적 멤버가 될 수 없습니다. 호출 할 함수 결정. 한 클래스에서 선언 된 가상 함수는 다른 클래스에서 친구로 선언 될 수 있습니다.] "

처음에는 '친구'가 기본 클래스의 구성원이 아니기 때문에 파생 클래스에서 어떻게 상속받을 수 있습니까?


우정은 멤버와 같은 선언을 통해 주어졌지만 실제로는 멤버가 아니라 다른 클래스가 본질적으로 '실제'멤버의 가시성 분류를 무시할 수 있다는 알림입니다. 인용 한 사양 섹션에서는 일관된 용어로 이러한 뉘앙스 및 프레임 동작과 관련하여 언어가 작동하는 방식을 설명하지만, 상황이 다르게 만들어 졌을 수 있으며, 불행히도 위의 어떤 것도 근거의 핵심에 도달하지 않습니다.
제프

0

클래스의 Friend 함수는 함수에 extern 속성을 할당합니다. 즉, extern은 함수가 클래스의 어딘가에 선언되고 정의되었음을 의미합니다.

따라서 친구 기능이 클래스의 구성원이 아님을 의미합니다. 따라서 상속은 외부 사물이 아닌 클래스의 속성 만 상속하도록 허용합니다. 또한 친구 함수에 상속이 허용되면 타사 클래스가 상속됩니다.


0

친구는 컨테이너의 스타일 인터페이스와 같은 상속에 능숙하지만 처음 말했듯이 C ++에는 전파 가능한 상속이 부족합니다.

class Thing;

//an interface for Thing container's
struct IThing {
   friend Thing;
   protected:
       int IThing_getData() = 0;
};

//container for thing's
struct MyContainer : public IThing {
    protected: //here is reserved access to Thing
         int IThing_getData() override {...}
};

struct Thing {
    void setYourContainer(IThing* aContainerOfThings) {
        //access to unique function in protected area 
        aContainerOfThings->IThing_getData(); //authorized access
    }
};

struct ChildThing : public Thing {
    void doTest() {
        //here the lack of granularity, you cannot access to the container.
        //to use the container, you must implement all 
        //function in the Thing class
        aContainerOfThings->IThing_getData(); //forbidden access
    }
};

나에게 C ++의 문제는 어디서나 모든 액세스를 제어 할 수있는 아주 좋은 세분성이 부족하다는 것입니다.

friend Thing은 friend Thing. *이되어 Thing의 모든 자식에게 액세스 권한을 부여 할 수 있습니다.

또한, 정확한 액세스 권한을 부여하는 friend [named area] Thing. *는 친구에 대한 특수 명명 된 영역을 통해 Container 클래스에 있습니다.

좋아 그만 꿈. 그러나 지금, 당신은 친구의 흥미로운 사용법을 알고 있습니다.

다른 순서로, 당신은 또한 모든 클래스가 자신에게 친숙하다는 것을 알고 흥미로운 것을 발견 할 수 있습니다. 즉, 클래스 인스턴스는
제한없이 동일한 이름의 다른 인스턴스의 모든 멤버를 호출 할 수 있습니다 .

class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from 
             //another object instance
             anotherObject)->test(); 
         }
};

0

간단한 논리 : '제인 친구가 있어요. 어제 우리가 친구가되었다고해서 그녀의 모든 친구가 내 친구가되는 것은 아닙니다. '

나는 여전히 그러한 개인적인 우정을 인정해야하며, 그에 따른 신뢰 수준이 될 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.