상속과 다형성의 언어별 차이

skycloud의 이미지

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
//오류

이렇게 나옵니다.

이렇게 다른 이유는 무엇인지 궁금합니다.

익명 사용자의 이미지

1. 정말입니까? 확실해요?

제가 돌려 보니 아래와 같이 출력되는군요.

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

2. 언어 만든 사람 마음입니다.

Quote:
이렇게 다른 이유는 무엇인지 궁금합니다.

가장 직접적인 답은 "두 언어의 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: 상속된 이름을 숨기는 일을 피하자" 부분을 참조하시면 좋겠군요. 주요한 대목을 인용합니다.

Quote:
이렇게 동작하는 데에는 다 그만한 이유가 있다는 이야기를 드리고 싶습니다. 여러분이 어떤 라이브러리 혹은 응용프로그램 프레임워크를 이용하여 파생 클래스를 하나 만들 때, 멀리 떨어져 있는 기본 클래스로부터 오버로드 버전을 상속시키는 경우를 막겠다는 것입니다. 일종의 실수로 간주하겠다는 것인데, 오버로드 버전을 상속했으면 하는 프로그래머가 우리 주변에 넘고 넘치니 애석한 일입니다. 사실, public 상속을 버젓이 쓰면서 기본 클래스의 오버로드 함수를 상속받지 않겠다는 것도 엄연히 is-a 관계 위반입니다. is-a 관계는 바로 public 상속의 진수라고 항목 32에서 강조하지 않았습니까? 사정이 이러하니, C++가 기본적으로 해 버리는 '상속된 이름 가리기'를 무시하고 시은 경우가 거의 대부분일 것입니다.

가려진 이름은 using 선언을 써서 끄집어낼 수 있습니다.

보시다시피, 프로그래머는 C++의 기본값을 무시하고 숨겨진 이름을 끄집어낼 수 있습니다.

"의도적으로 오버로딩 된 멤버 함수 중 하나만 오버라이드하고자 한다"는 점을 명시하는 과정이라고 볼 수 있겠지요.

3. 종합적으로 보면, kotlin 코드와 동일한 동작을 하는 C++ 코드는 아래와 같이 만들 수 있겠군요.

#include <iostream>
#include <memory>
 
class top{
public:
    virtual void aaa(){ std::cout << "aaa\n"; } // 가상 함수로 만들어서 동적 바인딩 활성화
    void aaa(int par){ std::cout << "ccc\n"; }
 
    virtual ~top() = default;
};
 
class middle: public top{
public:
    using top::aaa; // 기본 클래스(top)의 aaa 멤버 함수를 끄집어냅니다.
 
    void aaa() override { std::cout << "bbb\n"; }
};
 
int main(){
    std::unique_ptr<top> a = std::make_unique<top>();
    std::unique_ptr<top> b = std::make_unique<middle>();
    std::unique_ptr<middle> c = std::make_unique<middle>();
 
    a->aaa();
    b->aaa();
    c->aaa();
    c->aaa(10);
 
    return 0;
}

타 언어를 쓰다가 오신 분들 입장에서 까다롭고 불친절하다고 불평할 수는 있는데, 별로 소용없어요. C++의 철학이고, C++의 선택이지요.

skycloud의 이미지

코드 내용은 복사하다가 실수를 한 듯합니다. 말씀하신 결과가 맞네요
제가 실력이 부족하여 바로 이해는 안되서 여러번 봐야할 것같습니다. 감사합니다

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.