상속과 다형성의 언어별 차이
글쓴이: skycloud / 작성시간: 수, 2021/12/15 - 8:13오후
open class top{
open fun aaa(){
println("aaa")
}
open fun aaa(par:Int){
println("ccc")
}
}
open class middle : top() {
override fun aaa() {
println("bbb")
}
}
fun main(){
var a : top = top()
var b : top = middle()
var c : middle = middle()
a.aaa()
b.aaa()
c.aaa()
c.aaa(10)
}
코틀린은 이렇게 했을 때
결과가
aaa
bbb
bbb
ccc
로 나옵니다.
c++의 경우는 선언당시 타입에 의존하고 오버라이딩 되었을 때 기존 오버로딩된 다른 함수는 자식에서 사용이 불가능한데
코틀린은 위와 같이 자식에 불러도 문제 없더라구요
즉 c++로 동일하게 코드를 짠 결과는
aaa
aaa
bbb
//오류
이렇게 나옵니다.
이렇게 다른 이유는 무엇인지 궁금합니다.
Forums:
1. 정말입니까? 확실해요?
1. 정말입니까? 확실해요?
제가 돌려 보니 아래와 같이 출력되는군요.
출력:
2. 언어 만든 사람 마음입니다.
가장 직접적인 답은 "두 언어의 spec이 각각 그렇게 되도록 작성되었기 때문" 입니다.
물론 그런 답을 들으려 질문을 올리진 않았을 것이고, 아마 그렇게 되도록 작성한 이유를 알고 싶으시겠죠.
어려운 질문입니다. 원래 프로그래밍 언어를 설계하는 과정에서는 다양한 design choice가 이루어지게 되는데, 보통 이런 choice는 (1) 언어 설계 철학에 따르거나 (2) 언어 설계자의 취향 (or 변덕)에 따르거나 (3) 그냥 예전부터 했던 대로 했거나 (i.e., 경로 의존성) 하여튼 다양한 이유로 이루어지기 마련이거든요.
유감스럽게도 그런 질문에 답하기에는 제가 Kotlin을 잘 모릅니다. C++는 그나마 약간 아는 편입니다.
(1) 멤버 함수의 동적 바인딩
C++에서 포인터 혹은 참조자가 가리키고 있는 객체의 멤버 함수를 호출하려 할 때, 기본값은 포인터 혹은 참조자의 타입에 따라 멤버 함수를 호출하는 것입니다.
Kotlin의 예에서 보이는 것처럼 객체의 실제 타입에 따라 멤버 함수를 호출하기 위해서는 (cf. 이런 걸 동적 바인딩(dynamic binding)이라고 합니다.) 런타임에 호출할 멤버 함수를 결정해야 하고, 따라서 모든 객체가 자신의 타입 정보를 가지고 있어야 하는데요.
이 기능은 보통 가상 테이블(vtable)과 가상 테이블 포인터(vptr)의 형태로 구현됩니다.
모든 객체가 포인터 한 개 분량만큼 커져야 하고 멤버 함수 호출 때마다 가상 테이블 참조를 해야 하는 등, 비용이 추가되지요.
그런데 C++의 주요 철학 중 하나는, 사용하지 않는 기능에 대해서 비용을 지불하는 경우를 최소화한다는 것입니다.
===
동적 바인딩 기능을 사용하고자 한다면, 좋습니다. 프로그래머가 직접 해당 멤버 함수에 virtual을 지정해 주면 됩니다. (i.e., 가상 함수)
그러면 앞서 말씀드린 모든 비용을 지불하여 해당 기능을 사용할 수 있게 됩니다.
하지만 기본값은 이 기능을 사용하지 않는 것이고, 그러면 컴파일러는 가상 테이블과 가상 테이블 포인터 등을 아예 안 만들어 버려서 비용을 아낍니다.
동적 바인딩을 디폴트로 지원하는 언어에서는 이런 비용이 존재한다는 것 자체를 의식하는 것조차 쉽지 않지요. 사실 뭐 요즘의 넉넉한 컴퓨팅 환경에서는 보통 눈에 잘 안 띄기도 합니다만.
(2) 상속된 이름 숨기기
기본 클래스에 오버로딩 된 멤버 함수가 있는데, 파생 클래스에서 그 중 하나만을 오버라이드했을 경우, 파생 클래스에서 다른 오버로딩 된 멤버 함수를 조용히 호출할 수 있는 것이 과연 좋은 일일까요?
파생 클래스에서 기본 클래스의 특정 멤버 함수를 오버라이드하여 그 동작을 변경했다면, 대체로 같은 이름의 오버로드 버전 멤버 함수들도 비슷하게 변경해야 할 가능성이 높지 않겠습니까?
C++는 이런 경우 대체로 프로그래머의 실수일 가능성이 높다고 판단한 것 같습니다.
Effective C++ (Scott Meyers 저, 곽용재 역) "항목 33: 상속된 이름을 숨기는 일을 피하자" 부분을 참조하시면 좋겠군요. 주요한 대목을 인용합니다.
보시다시피, 프로그래머는 C++의 기본값을 무시하고 숨겨진 이름을 끄집어낼 수 있습니다.
"의도적으로 오버로딩 된 멤버 함수 중 하나만 오버라이드하고자 한다"는 점을 명시하는 과정이라고 볼 수 있겠지요.
3. 종합적으로 보면, kotlin 코드와 동일한 동작을 하는 C++ 코드는 아래와 같이 만들 수 있겠군요.
타 언어를 쓰다가 오신 분들 입장에서 까다롭고 불친절하다고 불평할 수는 있는데, 별로 소용없어요. C++의 철학이고, C++의 선택이지요.
감사합니다.
코드 내용은 복사하다가 실수를 한 듯합니다. 말씀하신 결과가 맞네요
제가 실력이 부족하여 바로 이해는 안되서 여러번 봐야할 것같습니다. 감사합니다
댓글 달기