다형성에 대한 이해 / 요구 사항
이 용어가 컴퓨팅 과학에서 사용되는 다형성을 이해하려면 간단한 테스트와 정의에서 시작하는 데 도움이됩니다. 치다:
Type1 x;
Type2 y;
f(x);
f(y);
여기서, f()
일부 동작을 수행하고 값 소정되고 x
그리고 y
입력으로.
다형성을 나타내려면 고유 한 유형에 맞는 코드를 찾아서 실행하는 f()
두 가지 이상의 고유 한 유형 (예 : int
및 double
)의 값으로 작동 할 수 있어야합니다 .
다형성을위한 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__
문자열 리터럴 연결 및 매크로의 다른 고유 기능 (악한 상태 유지 ;-))
- 템플릿 및 매크로 테스트 시맨틱 사용법이 지원되지만 지원이 제공되는 방법을 인위적으로 제한하지 않습니다 (가상 디스패치가 정확히 일치하는 멤버 함수 대체를 요구하여 경향이 있기 때문에)
다형성을 지원하는 다른 메커니즘
약속 한 바와 같이, 완전성을 위해 몇 가지 주변 주제가 다루어집니다.
이 답변은 위의 코드가 다형성 코드, 특히 파라 메트릭 다형성 (템플릿 및 매크로)을 강화하고 단순화하기 위해 어떻게 결합되는지에 대한 논의로 끝납니다.
유형별 작업에 매핑하기위한 메커니즘
> 암시 적 컴파일러 제공 과부하
개념적으로 컴파일러 는 내장 유형에 대한 많은 연산자를 오버로드 합니다. 사용자 지정 오버로드와 개념적으로 다르지 않지만 간과하기 쉬운 것으로 표시됩니다. 예를 들어, 동일한 표기법을 사용하여 int
s 및 double
s를 추가 x += 2
하면 컴파일러에서 다음을 생성합니다.
그러면 과부하가 사용자 정의 유형으로 완벽하게 확장됩니다.
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()
되려면 [ ]은 유형에 맞는 코드를 찾고 실행하는 두 가지 이상의 고유 한 유형 (예 : int
및 double
)의 값으로 작동 할 수 있어야합니다 .
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 ++에서 일반적인 것처럼, 프로그래머에게는 다형성이 사용되는 경계를 자유롭게 제어 할 수 있습니다.