C ++의 다형성


129

AFAIK :

C ++은 세 가지 유형의 다형성을 제공합니다.

  • 가상 기능
  • 함수 이름 오버로딩
  • 연산자 과부하

위의 세 가지 유형의 다형성 외에도 다른 종류의 다형성이 있습니다.

  • 실행 시간
  • 컴파일 타임
  • 임시 다형성
  • 파라 메트릭 다형성

내가 알고 런타임 다형성 에 의해 달성 될 수있다 가상 함수정적 다형성 에 의해 달성 될 수 템플릿 함수

그러나 다른 두

임시 다형성 :

사용할 수있는 실제 유형의 범위가 유한하고 조합을 사용하기 전에 개별적으로 지정해야하는 경우이를 임시 다형성이라고합니다.

파라 메트릭 다형성 :

모든 코드가 특정 유형을 언급하지 않고 작성되어 여러 유형의 새로운 유형과 함께 투명하게 사용될 수있는 경우이를 파라 메트릭 다형성이라고합니다.

나는 거의 이해할 수 없다 :(

가능한 한 누구나 예를 들어 설명 할 수 있습니까? 이 질문에 대한 답변이 그들의 대학에서 많은 새로운 유월절에 도움이 되길 바랍니다.


30
실제로 C ++에는 파라 메트릭 (C ++에서 템플릿을 통한 고유성), 포함 (C ++에서 가상 메서드를 통한 하위 유형 지정), 오버로드 및 강제 (암시 적 변환)의 4 가지 다형성이 있습니다. 개념적으로 함수 오버로딩과 연산자 오버로딩 사이에는 큰 차이가 없습니다.
fredoverflow

그래서 내가 언급 한 웹 사이트가 많은 사람들을 오도하는 것 같습니다.
Vijay

@ zombie : 그 웹 사이트는 많은 좋은 개념을 다루지 만 용어 사용에서 정확하고 일관성이 없습니다 (예 : 가상 파견 / 런타임 다형성에 대해 이야기하기 시작하면 잘못된 다형성에 대해 많은 진술을합니다. 가상 디스패치에는 일반적으로 적용됩니다. 주제를 이미 이해했다면, 말한 내용과 관련이 있고 필요한 경고를 정신적으로 삽입 할 수 있지만 사이트를 읽어 보는 것은 어렵습니다 ....
Tony Delroy

일부 용어는 동의어에 가깝거나 다른 용어와 관련이 있지만 더 제한적입니다. 예를 들어, "ad-hoc polymorphism"이라는 용어는 필자의 경험에서 Haskell에서 주로 사용되었지만 "가상 함수"는 매우 밀접한 관련이 있습니다. 사소한 차이점은 "가상 함수"는 "늦게 바인딩"된 멤버 함수를 참조하는 객체 지향 용어입니다. "다중 디스패치"는 일종의 임시 다형성입니다. FredOverflow가 말했듯이 연산자와 함수 오버로딩은 기본적으로 동일합니다.
Steve314

나는 당신을 위해 당신의 서식을 수정했습니다. 편집 창 오른쪽에있는 도움말을 읽으십시오. 질문이 200 개 이상이고 3K가 넘는 사람은이 기본 사항을 알아야합니다. 또한 새 키보드를 구매할 수도 있습니다. 이 사람의 Shift 키가 간헐적으로 실패한 것 같습니다. 아, 그리고 : C ++ 에는 "템플릿 함수"와 같은 것은 없다 . 그러나 함수 템플릿이 있습니다.
sbi

답변:


219

다형성에 대한 이해 / 요구 사항

이 용어가 컴퓨팅 과학에서 사용되는 다형성을 이해하려면 간단한 테스트와 정의에서 시작하는 데 도움이됩니다. 치다:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

여기서, f()일부 동작을 수행하고 값 소정되고 x그리고 y입력으로.

다형성을 나타내려면 고유 한 유형에 맞는 코드를 찾아서 실행하는 f()가지 이상의 고유 한 유형 (예 : intdouble)의 값으로 작동 할 수 있어야합니다 .


다형성을위한 C ++ 메커니즘

명시 적 프로그래머 지정 다형성

f()다음 방법 중 하나로 여러 유형에서 작동 할 수 있도록 작성할 수 있습니다 .

  • 전처리 :

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
    
  • 과부하 :

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • 템플릿 :

    template <typename T>
    void f(T& x) { x += 2; }
    
  • 가상 파견 :

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch
    

다른 관련 메커니즘

내장 유형, 표준 변환 및 캐스팅 / 강제에 대한 컴파일러 제공 다형성은 다음과 같이 완전성에 대해 나중에 설명합니다.

  • 그들은 일반적으로 직관적으로 (는 "정당화 어쨌든 이해하고 아, 그 ,"반응)
  • 위의 메커니즘을 요구하고 사용하는 데있어 한계점에 영향을 미치며
  • 설명은 더 중요한 개념들로부터 산만하게 산만하다.

술어

추가 분류

위의 다형성 메커니즘을 감안할 때 다양한 방식으로 분류 할 수 있습니다.

  • 다형성 유형별 코드는 언제 선택됩니까?

    • 런타임 은 컴파일러가 실행하는 동안 프로그램이 처리 할 수있는 모든 유형의 코드를 생성해야하며 런타임에 올바른 코드가 선택됨을 의미합니다 ( virtual dispatch )
    • 컴파일 시간컴파일 중에 유형별 코드를 선택한다는 의미입니다. 이것의 결과 : 사용 된 다형성 메커니즘과 선택에 대한 인라이닝 선택에 따라 인수로 f위에서 만 호출 된 프로그램을 말 int하십시오 f(double). ( 가상 디스패치를 ​​제외한 위의 모든 메커니즘 )

  • 어떤 유형이 지원됩니까?

    • 임시 의미는 각 유형 (예 : 과부하, 템플릿 전문화)을 지원하기위한 명시 적 코드를 제공합니다. " 특별히 의미에 따라"이 유형에 대한 지원, 다른 "this"및 "that"역시 ;-) 지원을 명시 적으로 추가합니다 .
    • 파라 메트릭 의미는 특별히 지원하지 않는 다양한 파라미터 유형 (예 : 템플릿, 매크로)없이 함수를 사용하려고 할 수 있습니다. 템플릿 / 매크로 예상하는 것처럼 행동하는 것이 기능 / 사업자와 객체 1 템플릿 / 매크로 필요가 정확한 유형이, 무관으로, 그 일을 위해 모든 것을. C ++ 20에서 도입 한 "개념"은 그러한 기대를 표현하고 시행 합니다 . 여기에서 cppreference 페이지를 참조하십시오 .

      • 파라 메트릭 다형성은 오리 타이핑을 제공합니다. 제임스 휘트 콤 라일리 (James Whitcomb Riley)의 개념은 "오리처럼 걷고 새처럼 수영하고 오리처럼 like 거리는 새를 볼 때 새를 오리라고 부릅니다." .

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • 하위 유형 (일명 포함) 다형성을 사용하면 알고리즘 / 함수를 업데이트하지 않고도 새로운 유형을 작업 할 수 있지만 동일한 기본 클래스 (가상 디스패치)에서 파생되어야합니다.

1- 템플릿이 매우 유연합니다. SFINAE (참조 std::enable_if)는 파라 메트릭 다형성에 대한 여러 가지 기대치를 효과적으로 허용합니다. 예를 들어, 처리중인 데이터 유형에 .size()멤버가있을 때 하나의 함수를 사용하고 그렇지 않으면 필요하지 않은 다른 함수를 사용하도록 인코딩 할 수 있습니다 .size()(그러나 아마도 느리게 strlen()인쇄하거나 로그에 유용한 메시지). 템플릿이 특정 매개 변수로 인스턴스화 될 때 임시 동작 (특정 템플릿)을 매개 변수로 남겨 두거나 ( 부분 템플릿 전문화 ) 아닌 ( 전체 전문화 ) 임시 행동을 지정할 수도 있습니다 .

"다형성"

Alf Steinbach는 C ++ 표준 다형성 에서는 가상 디스패치를 ​​사용하는 런타임 다형성 만을 언급한다고 언급합니다. 일반 Comp. 공상 과학 C ++ 제작자 Bjarne Stroustrup의 용어집 ( http://www.stroustrup.com/glossary.html )에 따라 의미가 더 포괄적입니다 .

다형성-다른 유형의 엔티티에 단일 인터페이스를 제공합니다. 가상 함수는 기본 클래스에서 제공하는 인터페이스를 통해 동적 (런타임) 다형성을 제공합니다. 오버로드 된 함수 및 템플릿은 정적 (컴파일 타임) 다형성을 제공합니다. TC ++ PL 12.2.6, 13.6.1, D & E 2.9.

이 답변은 질문과 마찬가지로 C ++ 기능을 Comp와 관련시킵니다. 공상 과학 술어.

토론

C ++ 표준에서는 Comp보다 "다형성"의 폭이 좁은 정의를 사용합니다. 공상 과학 대한 지역 사회가 상호 이해를 보장하기 위해 당신의 청중을 고려 ...

  • 모호하지 않은 용어 사용 ( "이 코드를 다형성으로 만들 수 있습니까?"대신 "이 코드를 다른 유형에 재사용 할 수 있습니까?"또는 "가상 디스패치를 ​​사용할 수 있습니까?") 및 / 또는
  • 용어를 명확하게 정의하십시오.

그래도 훌륭한 C ++ 프로그래머가되기 위해 결정적인 것은 다형성이 실제로 당신을 위해 무엇을 하고 있는지 이해 하는 것입니다.

    "알고리즘"코드를 한 번 작성한 다음 여러 유형의 데이터에 적용

... 다양한 다형성 메커니즘이 실제 요구와 어떻게 일치하는지 잘 알고 있어야합니다.

런타임 다형성에 적합 :

  • 팩토리 메소드에 의해 처리 된 입력을 통해 Base*s 를 통해 처리되는 이기종 오브젝트 콜렉션 으로 분리
  • 구성 파일, 명령 행 스위치, UI 설정 등을 기반으로 런타임에 선택된 구현
  • 구현은 상태 머신 패턴과 같이 런타임에 다양합니다.

런타임 다형성에 대한 명확한 드라이버가없는 경우 종종 컴파일 옵션이 선호됩니다. 치다:

  • 런타임에 실패한 팻 인터페이스보다 템플릿 클래스의 컴파일이라는 것이 선호됩니다.
  • SFINAE
  • CRTP
  • 최적화 (인라인 및 데드 코드 제거, 루프 언 롤링, 정적 스택 기반 어레이 대 힙 포함)
  • __FILE__,, __LINE__문자열 리터럴 연결 및 매크로의 다른 고유 기능 (악한 상태 유지 ;-))
  • 템플릿 및 매크로 테스트 시맨틱 사용법이 지원되지만 지원이 제공되는 방법을 인위적으로 제한하지 않습니다 (가상 디스패치가 정확히 일치하는 멤버 함수 대체를 요구하여 경향이 있기 때문에)

다형성을 지원하는 다른 메커니즘

약속 한 바와 같이, 완전성을 위해 몇 가지 주변 주제가 다루어집니다.

  • 컴파일러 제공 과부하
  • 전환
  • 캐스트 / 강제

이 답변은 위의 코드가 다형성 코드, 특히 파라 메트릭 다형성 (템플릿 및 매크로)을 강화하고 단순화하기 위해 어떻게 결합되는지에 대한 논의로 끝납니다.

유형별 작업에 매핑하기위한 메커니즘

> 암시 적 컴파일러 제공 과부하

개념적으로 컴파일러 는 내장 유형에 대한 많은 연산자를 오버로드 합니다. 사용자 지정 오버로드와 개념적으로 다르지 않지만 간과하기 쉬운 것으로 표시됩니다. 예를 들어, 동일한 표기법을 사용하여 ints 및 doubles를 추가 x += 2하면 컴파일러에서 다음을 생성합니다.

  • 유형별 CPU 명령어
  • 같은 유형의 결과.

그러면 과부하가 사용자 정의 유형으로 완벽하게 확장됩니다.

std::string x;
int y = 0;

x += 'c';
y += 'c';

기본 유형에 대한 컴파일러 제공 과부하는 고급 (3GL +) 컴퓨터 언어에서 일반적이며 다형성에 대한 명시 적 논의는 일반적으로 더 많은 것을 의미합니다. (2GL-어셈블리 언어-종종 프로그래머가 다른 유형에 대해 다른 니모닉을 명시 적으로 사용해야합니다.)

> 표준 전환

C ++ Standard의 네 번째 섹션에서는 표준 변환에 대해 설명합니다.

첫 번째 요점은 다음과 같이 훌륭하게 요약합니다 (오래된 초안에서-여전히 실질적으로 정확함).

-1- 표준 변환은 내장 유형에 대해 정의 된 암시 적 변환입니다. 조항 전환은 그러한 전환의 전체 세트를 열거합니다. 표준 변환 순서는 다음 순서로 표준 변환 순서입니다.

  • lvalue-to-rvalue 변환, array-to-pointer 변환 및 function-to-pointer 변환 세트에서 0 또는 1 변환.

  • 정수 승격, 부동 소수점 승격, 정수 변환, 부동 소수점 변환, 부동 정수 변환, 포인터 변환, 멤버 대 포인터 변환 및 부울 변환과 같은 집합에서 0 또는 1 개의 변환.

  • 0 또는 1 개의 자격 변환.

[참고 : 표준 변환 순서는 비어있을 수 있습니다. 즉 변환으로 구성 될 수 없습니다. ] 필요한 경우 표준 변환 순서가 표현식에 적용되어 필요한 대상 유형으로 변환됩니다.

이러한 변환은 다음과 같은 코드를 허용합니다.

double a(double x) { return x + 2; }

a(3.14);
a(42);

이전 테스트 적용 :

다형성이 a()되려면 [ ]은 유형에 맞는 코드를 찾고 실행하는가지 이상의 고유 한 유형 (예 : intdouble)의 값으로 작동 할 수 있어야합니다 .

a()자체적으로 코드를 구체적으로 실행 double하므로 다형성 이 아닙니다 .

그러나 두 번째 a()컴파일러 호출 에서는로 변환 42할 "부동 소수점 승격"(표준 §4)에 적합한 유형의 코드를 생성해야 합니다 42.0. 그 추가 코드는 호출 함수에 있습니다. 결론에서 이것의 중요성에 대해 논의 할 것입니다.

> 강제, 캐스트, 암시 적 생성자

이러한 메커니즘을 통해 사용자 정의 클래스는 내장 유형의 표준 변환과 유사한 동작을 지정할 수 있습니다. 한번 보자 :

int a, b;

if (std::cin >> a >> b)
    f(a, b);

여기서 개체 std::cin는 변환 연산자를 사용하여 부울 컨텍스트에서 평가됩니다. 이는 개념적으로 "통합 프로모션"과 함께 위의 주제에서 표준 변환으로 그룹화 될 수 있습니다.

암시 적 생성자는 실제로 동일한 작업을 수행하지만 캐스트 유형에 의해 제어됩니다.

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

컴파일러가 제공 한 과부하, 변환 및 강제의 영향

치다:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

우리는 양이 원하는 경우 x(즉, 6.5보다는 6 둥근 다운 수) 부문 중 실수로 취급, 우리는 단지 로 변경이 필요합니다 typedef double Amount.

훌륭하지만 , 코드를 명시 적으로 "유형"으로 만들기에는 너무 많은 작업 이 없었 습니다.

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

그러나 첫 번째 버전을 다음으로 변환 할 수 있다고 생각하십시오 template.

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

"편의 기능"이 적기 때문에 의도 한대로 int또는 인스턴스에 대해 쉽게 인스턴스화 할 수 있습니다 double. 이러한 기능이 없으면 명시 적 캐스트, 유형 특성 및 / 또는 정책 클래스, 다음과 같은 상세하고 오류가 발생하기 쉬운 혼란이 필요합니다.

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

따라서 내장 유형, 표준 변환, 캐스팅 / 강제 / 암시 적 생성자를위한 컴파일러 제공 연산자 오버로드는 모두 다형성에 대한 미묘한 지원을 제공합니다. 이 답변의 맨 위에있는 정의에서 다음을 매핑하여 "유형에 맞는 코드 찾기 및 실행"을 해결합니다.

  • 매개 변수 유형에서 "멀리"

    • 로부터 많은 데이터 타입 알고리즘 코드 핸들 다형성

    • (동일한 또는 다른) 유형의 (잠재적으로 더 적은) 개수 기록 번호.

  • 상수 유형의 값에서 "to"매개 변수 유형

다형성 컨텍스트를 자체적으로 설정 하지는 않지만 해당 컨텍스트 내에서 코드를 강화 / 단순화하는 데 도움이됩니다.

당신은 속이는 느낌이들 수 있습니다 ...별로 보이지 않습니다. 중요한 것은 파라 메트릭 다형성 컨텍스트 (예 : 템플릿 또는 매크로 내부)에서 임의로 광범위한 유형을 지원하려고하지만 종종 다른 함수, 리터럴 및 작업에 대해 설계된 작업을 표현하려고합니다. 작은 유형의 세트. 연산 / 값이 논리적으로 동일 할 때 거의 동일한 기능 또는 데이터를 유형별로 생성 할 필요성이 줄어 듭니다. 이러한 기능은 "최선의 노력"이라는 태도를 추가하고 제한된 기능과 데이터를 사용하여 직관적으로 예상되는 작업을 수행하고 실제 모호성이있을 때 오류만으로 중지합니다.

이를 통해 다형성 코드를 지원하는 다형성 코드의 필요성을 제한하고, 다형성 사용에 대해 더 긴밀한 네트워크를 도출하여 현지화 된 사용으로 인해 광범위하게 사용되지 않으며, 구현시 노출 비용을 부과하지 않고도 필요에 따라 다형성의 이점을 이용할 수 있습니다. 컴파일 시간, 객체 코드에 동일한 논리 함수의 여러 사본을 사용하여 사용 된 유형을 지원하고 인라인 또는 적어도 컴파일 타임이 해결 된 호출과 달리 가상 디스패치를 ​​수행합니다. C ++에서 일반적인 것처럼, 프로그래머에게는 다형성이 사용되는 경계를 자유롭게 제어 할 수 있습니다.


1
-1 용어 토론을 제외한 훌륭한 답변. C ++ 표준 §1.8 / 1에서 "다형성"이라는 용어를 정의 하며 가상 기능에 대한 10.3 절을 참조하십시오. 따라서 표준 방음 실에서 용어는 한 번만 정의됩니다. 그리고 실습 연습을한다. 예를 들어, §5.2.7 / 6 dynamic_cast에는 "다형성 유형의 포인터 또는 lvalue" 가 필요합니다. 건배 & hth.
건배와 hth. -Alf

@ Alf : 훌륭한 참조-귀하의 관점이 너무 좁다 고 생각합니다. 질문 목록 과부하, 애드혹 및 파라 메트릭 다형성 등은 C ++의 기능을 일반 Comp. 공상 과학 용어의 의미. 실제로 Stroustrup의 용어는 "다형성-다른 유형의 엔티티에 단일 인터페이스를 제공합니다. 가상 함수는 기본 클래스가 제공하는 인터페이스를 통해 동적 (런타임) 다형성을 제공합니다. 과부하 된 함수 및 템플릿은 정적 (컴파일 타임) 다형성을 제공합니다. TC ++ PL 12.2.6, 13.6.1, D & E 2.9. "
Tony Delroy 2016 년

@Tony : 그것은 당신의 대답의 주요 추력이 아닙니다. 괜찮습니다. 훌륭합니다. 그것은 단지 그 wrt입니다. 용어 당신은 그것을 거꾸로 : 공식 학문 용어는 신 국제 표준에 의해 정의 된 좁은 용어이며, 사람들이 약간 다른 것을 의미 할 수있는 비공식 거친 용어는이 질문과 답변에서 주로 사용됩니다. 건배 & hth.
건배와 hth. -Alf

@Alf : 나는 그 대답이 컸으면 좋겠다. "기타 메커니즘"은 5 분의 1 줄로 다시 작성해야하며, 다형성 메커니즘과 대조되는보다 구체적인 기능 및 시사점을 계획 / 제안하고있다. 어쨌든, 내 이해는 공식적인 학문적 독점적 C ++ 중심의 의미는 좁을 수 있지만 공식적인 학문적 일반 Comp. 공상 과학 Stroustrup의 용어집에서 알 수 있듯이 의미는 아닙니다. Knuth의 정의와 같이 결정적인 것이 필요하지만 아직 운이 없습니다. 나는 당신이 C ++ 전문가라는 것에 감사하지만, 이것에 대한 적절한 증거를 지적 할 수 있습니까?
Tony Delroy

1
@Alf : 둘째, 다형성은 공식적인 Comp에서 공식적으로 정의 된다고 확신합니다 . 공상 과학 내 사용 및 Stroustrup과 호환되는 (영원하고 안정적인) 방식으로 예약하십시오. Wikipedia 기사는 다음과 같은 방식으로이를 정의하는 몇 개의 학술 출판물을 연결합니다. "다형성 함수는 피연산자 (실제 매개 변수)가 둘 이상의 유형을 가질 수있는 함수입니다. 다형성 유형은 연산이 둘 이상의 유형의 값에 적용 가능한 유형입니다." ( lucacardelli.name/Papers/OnUnderstanding.A4.pdf에서 ). 그래서 문제는 "Comp. Sci를 말하는 사람"입니다 ...?
Tony Delroy 2016 년

15

C ++에서 중요한 차이점은 런타임 대 컴파일 타임 바인딩입니다. 나중에 설명 하겠지만 Ad-hoc vs. Parametric은 실제로 도움이되지 않습니다.

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

참고-런타임 다형성은 컴파일 타임에 여전히 해결 될 수 있지만 최적화 일뿐입니다. 런타임 해결을 효율적으로 지원하고 다른 문제와의 거래를 필요로하는 것은 가상 기능을 실제 기능으로 이끈 부분입니다. 그리고 이것이 C ++의 모든 형태의 다형성의 핵심입니다. 각각은 다른 맥락에서 만들어진 서로 다른 트레이드 오프 세트에서 발생합니다.

함수 오버로딩과 연산자 오버로딩은 모든면에서 중요합니다. 이름과 사용 구문은 다형성에 영향을 미치지 않습니다.

템플릿을 사용하면 한 번에 많은 기능 과부하를 지정할 수 있습니다.

동일한 해결 시간 아이디어에 대한 다른 이름 세트가 있습니다 ...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

이러한 이름은 OOP와 더 관련이 있으므로 템플릿 또는 다른 비 멤버 함수가 초기 바인딩을 사용한다는 것은 다소 이상합니다.

가상 함수와 함수 오버로드 간의 관계를 더 잘 이해하려면 "단일 디스패치"와 "다중 디스패치"의 차이점을 이해하는 것이 유용합니다. 아이디어는 진보로 이해 될 수 있습니다 ...

  • 첫째, 단형 함수가 있습니다. 함수의 구현은 함수 이름으로 고유하게 식별됩니다. 특별한 매개 변수는 없습니다.
  • 그런 다음 단일 디스패치가 있습니다. 매개 변수 중 하나는 특수한 것으로 간주되며 사용할 구현을 식별하기 위해 이름과 함께 사용됩니다. OOP에서는이 매개 변수를 "객체"로 생각하고 함수 이름 앞에 나열하는 경향이 있습니다.
  • 그런 다음 여러 디스패치가 있습니다. 모든 / 모든 매개 변수는 사용할 구현을 식별하는 데 기여합니다. 따라서 다시 한 번 매개 변수가 특별 할 필요는 없습니다.

하나의 매개 변수를 특별하게 지정하는 변명보다 OOP에는 분명히 더 많은 것이 있지만 그중 하나입니다. 그리고 트레이드 오프에 대해 내가 말한 것과 관련하여-단일 파견은 효율적으로 수행하기가 매우 쉽습니다 (일반적인 구현은 "가상 테이블"이라고 함). 다중 디스패치는 효율성 측면뿐만 아니라 별도의 컴파일 측면에서 더 어색합니다. 궁금한 점이 있으면 "표현 문제"를 찾아 볼 수 있습니다.

멤버가 아닌 함수에 "이른 바인딩"이라는 용어를 사용하는 것이 조금 이상한 것처럼, 컴파일 타임에 다형성이 해결되는 "단일 디스패치"및 "여러 디스패치"라는 용어를 사용하는 것은 조금 이상합니다. 일반적으로 C ++는 다중 디스패치를 ​​갖지 않는 것으로 간주되는데, 이는 특정 종류의 런타임 해결로 간주됩니다. 그러나 함수 오버로딩은 컴파일 타임에 다중 디스패치가 수행 된 것으로 볼 수 있습니다.

파라 메트릭 대 임시 다형성으로 돌아가서,이 용어는 함수형 프로그래밍에서 더 많이 사용되며 C ++에서는 작동하지 않습니다. 비록 그렇다 하더라도...

파라 메트릭 다형성이란 유형을 매개 변수로 사용하고 해당 매개 변수에 사용하는 유형에 관계없이 정확히 동일한 코드가 사용됨을 의미합니다.

Ad-hoc 다형성은 특정 유형에 따라 다른 코드를 제공한다는 점에서 ad-hoc입니다.

오버로드와 가상 함수는 둘 다 임시 다형성의 예입니다.

다시 한 번 동의어가 있습니다 ...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

이것들이 상당히 동의어가 아닌 것을 제외하면, 그것들은 일반적으로있는 것처럼 취급되지만 C ++에서 혼동이 일어날 가능성이있는 곳입니다.

이들을 동의어로 취급하는 이유는 다형성을 특정 유형의 유형으로 제한함으로써 해당 유형의 유형에 특정한 연산을 사용할 수 있다는 것입니다. 여기서 "클래스"라는 단어는 OOP 의미로 해석 될 수 있지만 실제로는 특정 작업을 공유하는 유형 (일반적으로 이름이 지정된) 집합을 나타냅니다.

따라서 매개 변수 다형성은 일반적으로 제한되지 않은 다형성을 암시하기 위해 (적어도 기본적으로) 취해집니다. 유형 매개 변수에 관계없이 동일한 코드가 사용되므로 모든 유형에 대해 작동하는 작업 만 지원됩니다. 유형 집합을 제한되지 않은 상태로두면 해당 유형에 적용 할 수있는 작업 집합을 심각하게 제한 할 수 있습니다.

예를 들어 Haskell에서는 다음을 수행 할 수 있습니다.

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

a여기에는 제약 다형성 유형입니다. 그것은 무엇이든 될 수 있으므로 해당 유형의 값으로 할 수있는 일은 많지 않습니다.

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

여기서는 숫자처럼 작동하는 유형 aNum클래스 로 제한됩니다 . 이 제약 조건을 사용하면 값을 추가하는 등의 값으로 수많은 작업을 수행 할 수 있습니다. 심지어는 3다형성 - 타입 추론 수치는 밖으로 당신이 의미하는 것을 3유형의 a.

저는 이것을 구속 된 파라 메트릭 다형성으로 생각합니다. 구현은 하나 뿐이지 만 제한된 경우에만 적용 할 수 있습니다. 애드혹 측면은의 선택 +3용도에 관한 것이다. 의 각 "인스턴스" Num에는 고유 한 구현이 있습니다. 따라서 Haskell에서도 "parametric"과 "unonstrained"는 동의어가 아닙니다. 나를 비난하지 마십시오. 내 잘못이 아닙니다!

C ++에서 오버로드와 가상 함수는 모두 임시 다형성입니다. 임시 다형성의 정의는 구현이 런타임에 선택되는지 컴파일 타임에 선택되는지는 중요하지 않습니다.

모든 템플릿 매개 변수에 유형이 있으면 C ++은 템플릿을 사용하여 파라 메트릭 다형성에 매우 가깝습니다 typename. 형식 매개 변수가 있으며 사용되는 형식에 관계없이 단일 구현이 있습니다. 그러나 "대체 실패는 오류가 아닙니다"규칙은 템플릿 내에서 작업을 사용한 결과로 암시 적 제약 조건이 발생 함을 의미합니다. 다른 복잡한 (임시) 구현 템플릿을 제공하기위한 템플릿 전문화가 추가로 복잡해집니다.

따라서 C ++에는 파라 메트릭 다형성이 있지만 암시 적으로 제한되며 임시 대안으로 재정의 될 수 있습니다. 즉이 분류는 실제로 C ++에서는 작동하지 않습니다.


흥미로운 포인트와 통찰력 +1. 나는 Haskell에 대해 읽는 데 몇 시간을 보냈기 때문에 " a여기서는 제한되지 않은 다형성 유형 [...]이므로 그 유형의 값으로 할 수있는 일은 많지 않습니다." C ++ sans Concepts에서는 템플릿 매개 변수로 지정된 유형의 인수에 대해서만 특정 작업 집합을 시도하는 것으로 제한되지 않습니다 ... 부스트 개념과 같은 라이브러리는 다른 방식으로 작동합니다-유형이 작업을 지원하는지 확인하십시오 실수로 추가 작업을 사용하지 않도록 지정하지 마십시오.
Tony Delroy

@Tony-개념은 템플릿의 다형성을 명시 적으로 제한하는 방법입니다. 호환성으로 인해 암시 적 제약 조건은 사라지지 않지만 명시 적 제약 조건으로 인해 크게 개선 될 것입니다. 나는 개념에 대한 과거 계획이 하스켈 유형 클래스와 다소 관련이 있다고 확신하지만, 그 개념을 깊이 조사하지 않았고 마지막으로 "얕게"보았을 때 나는 많은 하스켈을 알지 못했습니다.
Steve314

메모리에서 C ++ 0x Concepts는 "암시 적 제약 조건"을 막았다 (:-/로 약속).
Tony Delroy

2

임시 다형성에 관해서는 함수 과부하 또는 연산자 과부하를 의미합니다. 여기를 확인하십시오 :

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

파라 메트릭 다형성과 관련하여 템플릿 함수는 반드시 FIXED 유형의 매개 변수를 사용할 필요가 없으므로 계산할 수 있습니다. 예를 들어, 하나의 함수는 정수 배열을 정렬 할 수 있고 문자열 배열 등을 정렬 할 수도 있습니다.

http://en.wikipedia.org/wiki/Parametric_polymorphism


1
불행히도, 정확하지만 이것은 오도의 소지가 있습니다. 템플릿 함수는 SFINAE 규칙으로 인해 암시 적 제약 조건을 얻을 수 있습니다. 템플릿 내에서 작업을 사용하면 다형성이 암시 적으로 제한됩니다. 템플릿 특수화는보다 일반적인 템플릿을 무시하는 임시 대체 템플릿을 제공 할 수 있습니다. 따라서 템플릿 (기본적으로)은 제한되지 않은 파라 메트릭 다형성을 제공하지만 적용 할 수는 없습니다. 제한되거나 특별하게 될 수있는 두 가지 방법이 있습니다.
Steve314

실제로 귀하의 예-정렬-은 제약 조건을 의미합니다. 정렬은 순서가 지정된 유형에 대해서만 작동합니다 (예 : <유사한 연산자 제공). Haskell에서는 class를 사용하여 해당 요구 사항을 명시 적으로 표현합니다 Ord. <특정 유형 (의 인스턴스에서 제공 한)에 따라 달라지는 사실은 Ord임시 다형성으로 간주됩니다.
Steve314

2

이것은 어떤 도움이되지 않을 수도 있습니다,하지만 나는이 같은 정의 기능을 제공하여 프로그래밍에 내 친구를 소개하게 START하고, END(그들은에만 사용되는 기본 기능에 너무 발굴되지 않도록 MAIN.CPP의 파일). 여기에는 다형성 클래스와 구조체, 템플릿, 벡터, 배열, 선행 지시자, 우정, 연산자 및 포인터가 포함됩니다 (다형성을 시도하기 전에 알아야 할 모든 것).

참고 : 완료되지 않았지만 아이디어를 얻을 수 있습니다

main.cpp

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

main.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};

1

다음은 다형성 클래스를 사용하는 기본 예입니다.

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}

0

다형성 (polymorphism)은 다양한 형태를 의미하므로 운영자가 서로 다른 상황에서 다르게 행동하는 데 사용됩니다. 다형성은 상속을 구현하는 데 사용됩니다. 예를 들어, 클래스 모양에 대해 fn draw ()를 정의한 다음 draw fn은 원, 상자, 삼각형 및 기타 모양을 그리기 위해 구현 될 수 있습니다. (클래스 모양의 객체)


-3

이 사람들에게 CUT이라고하면

The Surgeon
The Hair Stylist
The Actor

무슨 일이 일어날 것?

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

위의 표현은 OOP에서 다형성 (동일한 이름, 다른 동작)이란 무엇입니까?

인터뷰를하려고하는데 면접관이 우리가 앉아있는 같은 방에서 다형성에 대한 생생한 예를 말하고 보여 주면

답변-문 / 창문

어떻게 궁금하십니까?

문 / 창을 통해-사람이 올 수 있고, 공기가 올 수 있고, 빛이 올 수 있으며, 비가 올 수 있습니다.

즉 하나의 형태가 다른 행동 (다형성).

더 잘 이해하고 간단한 방법으로 위의 예제를 사용했습니다. 코드에 대한 참조가 필요한 경우 위의 답변을 따르십시오.


c ++에서 다형성에 대한 이해를 돕기 위해 언급했듯이 위 예제를 사용했습니다. 이것은 신입생이 실제로 인터뷰에서 수행하는 동안 코드 뒤에서 의미와 의미가 무엇인지 이해하고 연관시키는 데 도움이 될 수 있습니다. 감사합니다!
Sanchit

op는 "C ++의 다형성"을 물었다. 당신의 대답은 너무 추상적입니다.
StahlRat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.