const가 아닌 메서드가 private 일 때 public const 메서드가 호출되지 않는 이유는 무엇입니까?


117

이 코드를 고려하십시오.

struct A
{
    void foo() const
    {
        std::cout << "const" << std::endl;
    }

    private:

        void foo()
        {
            std::cout << "non - const" << std::endl;
        }
};

int main()
{
    A a;
    a.foo();
}

컴파일러 오류는 다음과 같습니다.

오류 : 'void A :: foo ()'는 비공개입니다.

그러나 개인 정보를 삭제하면 작동합니다. const가 아닌 메서드가 private 일 때 public const 메서드가 호출되지 않는 이유는 무엇입니까?

즉, 액세스 제어보다 과부하 해결이 필요한 이유는 무엇입니까? 이건 이상해. 일관성이 있다고 생각하십니까? 내 코드가 작동하고 메서드를 추가하면 작업 코드가 전혀 컴파일되지 않습니다.


3
C ++에서는 PIMPL 관용구를 사용하는 것과 같은 추가 노력 없이는 클래스의 실제 "개인"부분이 없습니다. 이것은 문제 중 하나 일뿐입니다 ( "비공개"메서드 오버로드를 추가하고 컴파일 오래된 코드를 깨는 것은 내 책에서 문제로 간주됩니다. 비록 이것이 그렇게하지 않는 것으로 피하는 것이 사소한 일이더라도).
hyde

const 함수를 호출 할 수 있지만 상수가 아닌 상대가 개인 인터페이스의 일부가 될 것으로 예상되는 실제 코드가 있습니까? 이것은 나에게 나쁜 인터페이스 디자인처럼 들립니다.
Vincent Fourmond

답변:


125

를 호출 a.foo();하면 컴파일러는 사용하기에 가장 좋은 함수를 찾기 위해 오버로드 확인을 거칩니다. 과부하 세트를 빌드 할 때

void foo() const

void foo()

이제 a가 아니기 때문에 const상수가 아닌 버전이 가장 적합하므로 컴파일러는 void foo(). 그런 다음 액세스 제한이 적용되고 void foo()비공개 이므로 컴파일러 오류가 발생 합니다.

과부하 해결에서는 '사용 가능한 최상의 기능 찾기'가 아닙니다. '가장 좋은 기능을 찾아서 사용 해보자'입니다. 액세스 제한 또는 삭제로 인해 불가능한 경우 컴파일러 오류가 발생합니다.

즉, 액세스 제어보다 과부하 해결이 필요한 이유는 무엇입니까?

글쎄, 봅시다 :

struct Base
{
    void foo() { std::cout << "Base\n"; }
};

struct Derived : Base
{
    void foo() { std::cout << "Derived\n"; }
};

struct Foo
{
    void foo(Base * b) { b->foo(); }
private:
    void foo(Derived * d) { d->foo(); }
};

int main()
{
    Derived d;
    Foo f;
    f.foo(&d);
}

이제 내가 실제로 void foo(Derived * d)비공개 로 만들려는 것이 아니라고 가정 해 봅시다 . 액세스 제어가 먼저 발생하면이 프로그램이 컴파일되고 실행되고 Base인쇄됩니다. 이것은 대규모 코드베이스에서 추적하기가 매우 어려울 수 있습니다. 액세스 제어는 오버로드 해결 후에 이루어지기 때문에 호출하려는 함수를 호출 할 수 없다는 멋진 컴파일러 오류가 발생하고 버그를 훨씬 쉽게 찾을 수 있습니다.


과부하 해결 후에 액세스 제어가 이루어지는 이유가 있습니까?
drake7707

3
@ drake7707 액세스 제어가 먼저 발생하면 위의 코드가 컴파일되어 프로그램의 의미를 변경하는 코드 샘플에서 볼 수 있습니다. 당신에 대해 잘 모르겠지만, 함수가 비공개로 유지되기를 원하면 암시 적 캐스트와 코드가 조용히 "작동"하려면 오류가 발생하고 명시 적 캐스트를 수행해야합니다.
NathanOliver

"함수를 비공개로 유지하려면 명시 적 캐스트를 수행해야합니다."-여기서 진짜 문제는 암시 적 캐스트 인 것처럼 들립니다. 반면에 파생 클래스를 암시 적으로 사용할 수도 있다는 생각은 기본 클래스는 OO 패러다임을 정의하는 특징 이지요?
스티븐 Byks

35

궁극적으로 이것은 과부하 해결을 수행 할 때 접근성을 고려해서는 안된다는 표준의 주장으로 귀결됩니다 . 이 주장은 [over.match] 절 3 에서 찾을 수 있습니다 .

... 과부하 해결이 성공하고 사용 가능한 컨텍스트에서 실행 가능한 최상의 함수에 액세스 할 수없는 경우 (Clause [class.access]) 프로그램은 잘못된 형식입니다.

또한 같은 섹션의 1 항에 있는 주석 :

[참고 : 과부하 해결에 의해 선택된 기능이 상황에 적합하다고 보장 할 수 없습니다. 함수의 접근성과 같은 다른 제한으로 인해 호출 컨텍스트에서 잘못된 형식을 사용할 수 있습니다. — 끝 참고]

그 이유는 몇 가지 가능한 동기를 생각할 수 있습니다.

  1. 오버로드 후보의 접근성 변경으로 인한 예기치 않은 동작 변경을 방지합니다 (대신 컴파일 오류가 발생 함).
  2. 오버로드 해결 프로세스에서 컨텍스트 의존성을 제거합니다 (즉, 오버로드 해결은 클래스 내부 또는 외부에서 동일한 결과를 나타냄).

32

액세스 제어가 과부하 해결 전에 왔다고 가정합니다. 사실상 이것은 public/protected/private접근성보다는 통제 된 가시성을 의미합니다 .

StroustrupDesign and Evolution of C ++ 의 섹션 2.10 에는 이에 대한 구절이 있으며 여기에서 다음 예제를 논의합니다.

int a; // global a

class X {
private:
    int a; // member X::a
};

class XX : public X {
    void f() { a = 1; } // which a?
};

스트로브 스트 룹은 현재의 규칙 (접근성 전에 가시성)의 혜택 (일시적으로)을 chaning이 있음을 언급 private내부 class Xpublic(예를 들어, 디버깅의 목적을 위해) 위의 프로그램의 의미에는 조용한 변화 (즉이 없다는 것입니다 X::a에 시도 두 경우 모두 액세스 할 수 있으며 위의 예에서 액세스 오류가 발생 함). 경우 public/protected/private가시성을 제어 할 프로그램의 의미는 (글로벌이 변경됩니다 a호출 할 것이다 private, 그렇지 않으면,X::a ).

그런 다음 그는 이것이 명시적인 설계에 의한 것인지 아니면 표준 C ++에 대한 Classess 이전 버전으로 C를 구현하는 데 사용 된 전 처리기 기술의 부작용인지 기억하지 못한다고 말합니다.

이것이 당신의 예와 어떤 관련이 있습니까? 기본적으로 표준에서 오버로드 확인을 만들었 기 때문에 이름 조회가 액세스 제어보다 먼저 발생한다는 일반적인 규칙을 준수합니다.

10.2 멤버 이름 조회 [class.member.lookup]

1 멤버 이름 조회는 클래스 범위 (3.3.7)에서 이름 (id-expression)의 의미를 결정합니다. 이름 조회로 인해 모호성이 발생할 수 있으며이 경우 프로그램의 형식이 잘못되었습니다. id-expression의 경우 이름 조회는 this의 클래스 범위에서 시작됩니다. 규정 된 ID의 경우 이름 조회는 nestedname- 지정자의 범위에서 시작됩니다. 이름 조회는 액세스 제어 전에 발생합니다 (3.4, 11 절).

8 오버로드 된 함수의 이름이 모호하지 않은 경우 액세스 제어 전에 오버로드 해결 (13.3)도 발생 합니다. 모호성은 종종 클래스 이름으로 이름을 한정하여 해결할 수 있습니다.


23

암시 적 this포인터가 non- const이므로 컴파일러는 먼저 const버전 이전에 함수의 버전이 아닌지 여부를 확인합니다 const.

non- constone private을 명시 적으로 표시하면 해결이 실패하고 컴파일러는 검색을 계속하지 않습니다.


일관성이 있다고 생각하십니까? 내 코드가 작동하고 메서드를 추가하면 작업 코드가 전혀 컴파일되지 않습니다.
Narek

그렇게 생각합니다. 과부하 해결은 의도적으로 까다 롭습니다. 어제 비슷한 질문에 대답 : stackoverflow.com/questions/39023325/...
밧세바에게

5
@Narek 나는 그것이 오버로드 해결에서 삭제 된 함수가하는 것과 똑같이 작동한다고 믿습니다. 세트에서 가장 좋은 것을 선택하고 사용할 수 없음을 확인하여 컴파일러 오류가 발생합니다. 사용하기 가장 좋은 기능이 아니라 가장 좋은 기능을 골라서 사용하려고합니다.
NathanOliver

3
@Narek 나는 또한 그것이 작동하지 않는 이유를 처음으로 궁금해했지만 이것을 고려하십시오 : public const 객체를 비 const 객체에 대해서도 선택해야한다면 어떻게 private 함수를 호출합니까?
idclev 463035818

20

일어나는 일의 순서를 명심하는 것이 중요합니다.

  1. 모든 실행 가능한 기능을 찾으십시오.
  2. 가장 실행 가능한 기능을 선택하십시오.
  3. 정확히 하나의 최상의 실행 가능한 함수가 없거나 실제로 최상의 실행 가능한 함수를 호출 할 수없는 경우 (액세스 위반 또는 deleted 인 함수로 인해 ) 실패합니다.

(3)은 (2) 이후에 발생합니다. 이것은 정말 중요합니다. 그렇지 않으면 함수를 deleted 또는 만들면 private무의미하고 추론하기가 훨씬 더 어려워지기 때문입니다.

이 경우 :

  1. 실행 가능한 기능은 A::foo()A::foo() const입니다.
  2. 가장 실행 가능한 기능은 A::foo()후자가 암시 적 this인수 에 대한 자격 변환을 포함하기 때문 입니다.
  3. 그러나 A::foo()이다 private당신은 그것을 액세스, 따라서 코드가 잘못 형성되어이 없습니다.

1
"실행 가능"이 관련 액세스 제한을 포함한다고 생각할 수 있습니다. 즉, 해당 클래스의 공용 인터페이스의 일부가 아니므로 클래스 외부에서 개인 함수를 호출하는 것은 "실행 가능"하지 않습니다.
RM

15

이것은 C ++의 매우 기본적인 디자인 결정으로 귀결됩니다.

호출을 만족시키는 함수를 찾을 때 컴파일러는 다음과 같은 검색을 수행합니다.

  1. 해당 이름을 가진 무언가 가 있는 처음 1 개의 범위 를 찾기 위해 검색합니다 .

  2. 컴파일러는 해당 범위에서 해당 이름을 가진 모든 함수 (또는 펑터 등)를 찾습니다 .

  3. 그런 다음 컴파일러는 찾은 항목 중에서 가장 적합한 후보를 찾기 위해 오버로드 확인을 수행합니다 (액세스 가능 여부에 관계없이).

  4. 마지막으로 컴파일러는 선택한 함수에 액세스 할 수 있는지 확인합니다.

그 순서 때문에 액세스 할 수있는 다른 오버로드가 있더라도 컴파일러가 액세스 할 수없는 오버로드를 선택할 수 있습니다 (오버로드 해결 중에 선택되지 않음).

여부에 관해서는 될 사물을 다르게 할 : 예, 그것은 의심 할 여지없이 가능합니다. 그래도 C ++와는 확실히 다른 언어로 이어질 것입니다. 다소 사소 해 보이는 많은 결정은 처음에 명백한 것보다 훨씬 더 많은 영향을 미칠 수 있습니다.


  1. "First"는 그 자체로 약간 복잡 할 수 있습니다. 특히 템플릿이 포함 된 경우에는 2 단계 조회로 이어질 수 있기 때문에 검색을 수행 할 때 시작할 두 개의 완전히 분리 된 "루트"가 있습니다. 하지만 기본 아이디어는 매우 간단합니다. 가장 작은 둘러싸는 범위에서 시작하여 더 크고 더 큰 둘러싸는 범위로 나가십시오.

1
Stroustrup은 D & E에서이 규칙이 한 번 더 고급 컴파일러 기술이 사용 가능 해지면 검토되지 않은 클래스와 함께 C에서 사용되는 전 처리기의 부작용 일 수 있다고 추측합니다. 내 대답을 참조하십시오 .
TemplateRex

12

액세스 제어 (public , protected, private) 해상도를 과부하에 영향을 미치지 않습니다. 컴파일러 void foo()는 가장 잘 일치 하기 때문에 선택합니다 . 접근 할 수 없다는 사실은 그것을 바꾸지 않습니다. 그것을 제거하면 void foo() const, 만 남게되며 , 이는 최상의 (즉, 유일한) 일치입니다.


11

이 호출에서 :

a.foo();

this모든 멤버 함수 에는 항상 암시 적 포인터를 사용할 수 있습니다. 그리고의 const자격은 this호출 참조 / 객체에서 가져옵니다. 위의 호출 컴파일러에서 다음과 같이 처리 됩니다.

A::foo(a);

그러나 다음 과 같이 취급A::foo 되는 두 가지 선언이 있습니다 .

A::foo(A* );
A::foo(A const* );

과부하 분해능에 의해 첫 번째는 상수가 아닌 것으로 선택 this되고 두 번째는 const this. 첫 번째 항목을 제거하면 두 번째 항목이 const및 모두에 바인딩됩니다 non-const this.

최상의 실행 가능한 기능을 선택하기위한 과부하 해결 후 액세스 제어가 제공됩니다. 선택한 오버로드에 대한 액세스를로 지정 private했으므로 컴파일러가 불평합니다.

표준은 다음과 같이 말합니다.

[class.access / 4] : ... 함수 이름이 오버로드 된 경우 과부하 해결에서 선택한 함수에 대한 액세스 제어가 적용됩니다 ....

하지만 이렇게하면 :

A a;
const A& ac = a;
ac.foo();

그러면 const과부하 만 적합합니다.


그것은 가장 실행 가능한 기능을 선택하기 위해 과부하 해결 후 액세스 제어가 오는 이상한 것입니다 . 액세스 제어는 액세스 권한이없는 것처럼 과부하 해결 전에 이루어져야합니다. 전혀 고려하지 않아야한다고 생각하십니까?
Narek

@Narek, .. C ++ 표준에 대한 참조 로 답변을 업데이트했습니다 . C ++에는이 동작에 의존하는 많은 것들과 관용구가 있습니다.
WhiZTiM

9

기술적 이유는 다른 답변으로 답변되었습니다. 이 질문에만 집중하겠습니다.

즉, 액세스 제어보다 과부하 해결이 필요한 이유는 무엇입니까? 이건 이상해. 일관성이 있다고 생각하십니까? 내 코드가 작동하고 메서드를 추가하면 작업 코드가 전혀 컴파일되지 않습니다.

그것이 언어가 설계된 방법입니다. 의도는 가능한 한 최상의 실행 가능한 과부하를 호출하려는 것입니다. 실패하면 설계를 다시 고려하도록 상기시키는 오류가 트리거됩니다.

반면에 코드가 컴파일 const되고 호출되는 멤버 함수 와 잘 작동한다고 가정합니다 . 언젠가 누군가 (자신 일 수도 있음)가 비 const멤버 함수 의 접근성을에서으로 변경하기로 결정 private합니다 public. 그러면 컴파일 오류없이 동작이 변경됩니다! 이것은 놀라운 일 입니다.



8

액세스 지정자는 이름 조회 및 함수 호출 확인에 영향을주지 않습니다. 컴파일러가 호출이 액세스 위반을 트리거해야하는지 여부를 확인하기 전에 함수가 선택됩니다.

이렇게하면 액세스 지정자를 변경하면 기존 코드에 위반 사항이있는 경우 컴파일 타임에 경고가 표시됩니다. 함수 호출 해결을 위해 프라이버시가 고려되면 프로그램의 동작이 조용히 변경 될 수 있습니다.

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