2023. 10. 27. 14:59ㆍCS, 기술면접
오버로딩과 오버라이딩 단어가 비슷해서 많이 헷갈리는 내용이다. 하지만 키워드와 해석하면 이부분을 쉽게 이해할 수있을 것이다.
오버로딩 (overloading)
초과적재라는 뜻으로 말 그대로 같은이름의 메소드를 추가로 만드는 것이다.
즉 오버로딩은 같은 이름의 함수를 여러개 정의하고, 매개변수 타입과 개수 또는 반환형 달리해 다르게 호출하는 것이다.
- 이름은 같지만 시그니처 (파라미터 수, 타입)에 따라 다른 메소드를 중복 선언하는 것
- 같은 메소드라도 매개변수만 다르면 얼마든지 정의하고 사용할 수 있다.
오버로딩은 다음과 같은 조건이 붙는다.
1. 메소드 이름이 같아야 한다.
2. 리턴형(반환형)이 같아도 되고 달라도 된다.
3. 파라미터 개수가 달라도 되고, 같아도 된다.
4. 파라미터 개수가 같으면 데이터 타입(매개변수 타입)이 달라야 한다.
다음과 같은 예시가 있다. 다음은 계산하는 메서드 Calculate를 오버로딩한 것이다.
오버라이딩 (overriding)
재정의라는 뜻으로 메서드를 새로운 메서드로 재정의 한다는 의미이다.
클래스 상속구조에서 부모 클래스의 메서드를 자식클래스에서 같은 이름, 같은 매개변수의 함수를 재정의하는 것이다.
- 상속관계에 있는 클래스간에 같은 이름의 메서드를 정의하는 기술
- 자식클래스가 부모클래스에서 선언된 것과 같은 메소드를 가질 때, 메소드 오버라이딩이라함
- 부모로 부터 메소드의 로직(내부)를 환경(상황)에 맞게 변경함으로 다형성을 만들 수 있다.
오버라이딩은 다음과 같은 조건이 붙는다.
1. 오버라이드 하고자 하는 메소드가 상위 클래스에 존재 해야한다.
2. 메소드 이름이 같아야한다.
3. 메소드 파라미터 개수, 파라미터 타입이 같아야한다.
4. 리턴형(반환형)이 같아야 한다.
5. 상위 메소드와 동일하거나 추가되어야 한다.
다음은 클래스 상속구조에서 Run1 메서드를 오버라이딩한 것이다.
우선 케이스-A 의 경우 변수 a가 클래스 A 타입이고, A 타입의 메서드 Run1을 실행했으므로, 결과는 "A.Run1"이 된다. 하지만 케이스-B 의 경우, 클래스 B 객체를 생성하여 이를 A 타입의 변수에 할당했으므로 클래스 A의 Run1()을 실행할 것 같지만, 실제로는 B의 Run1()을 실행하게 된다.
이는 객체지향프로그래밍(OOP)의 다형성(Polymorphism)을 표현한 예인데, Polymorphism은 Base클래스의 레퍼런스를 사용하여 하위 계층에 있는 다양한 파생클래스의 (동일 signature) 메서드들 실행할 수 있게 하는 기능이다.
그러면, 어떠한 방식으로 이런 다형성이 가능한 것일까?
이것은 가상함수 테이블이라는것이 존재해서 가능하다.
가상함수 테이블 (VTable)
Virtual Table은 기본적으로 메서드 포인터들을 저장하는 배열로서 흔히 VTable, virtual function table, virtual method table 등으로도 불리운다.
이것은 한 클래스가 해당 클래스 혹은 상위 Base클래스에서 하나 이상의 가상메서드(virtual 혹은 abstract 메서드)를 갖는다면, 해당 클래스의 Method Table 메타데이타 내에 Virtual Table을 갖게 된다
사실 C# / .NET 에서 모든 클래스(혹은 struct)는 기본적으로 System.Object 클래스로부터 상속되므로 모든 클래스가 VTable을 갖는다. 즉, 모든 클래스는 System.Object 클래스의 4개의 가상메서드(ToString(), Equals(), GetHashCode(), Finalize() )를 자신의 VTable안에 갖게 된다. (주: Java는 모든 메서드가 디폴트로 가상메서드이다. C#은 virtual / abstract 를 지정해야 가상메서드가 된다)
만약 Base클래스가 가상메서드를 갖지 않는다면, 파생클래스는 Base 클래스의 메서드를 VTable 슬롯에 추가하지 않는다. 예를 들어, 다음과 같이 Base클래스 A의 메서드가 virtual 없이 선언 되었다면, 파생클래스의 VTable에는 A의 메서드 슬롯을 갖지 않는다.
아래 그림은 파생클래스 B의 VTable이 메모리상에서 표현된 것을 보여 주는데, 처음 4개의 메서드는 System.Object의 가상메서드들이고, 다음의 2개는 Base클래스 A의 가상메서드이다. 그리고 마지막으로 파생클래스 B의 virtual 혹은 nonvirtual 메서드들이 있게 된다 (해당 파생클래스의 메서드 슬롯에는 자신의 public 메서드 뿐만 아니라 private 메서드들도 추가된다). 파생클래스가 조부모, 부모 등의 여러 Hierarchy를 갖는다면, 최상위 Base클래스의 가상메서드부터 부모 클래스 그리고 해당 파생클래스까지 계층적인 순서대로 메서드 포인터 슬롯을 갖게된다.
다음과 같이 가상함수 테이블에 A, B객체의 구조를 확인할 수 이다.
그러면 이러한 사항들을 염두에 두고 다시 위의 두 케이스를 살펴보자.
먼저 케이스-A의 경우 클래스 A의 객체를 생성한 것이므로 다음과 같이 클래스 A의 VTable을 사용할 것이다. 이 VTable을 보면 a.Run1() 메서드 호출은 VTable에서 5번째 메서드 슬롯 즉 A.Run1()을 가리키는 포인터를 사용하게 된다.
이 VTable을 보면, x.Run1() 메서드 호출은 타입 A에서의 Run1 메서드 위치 즉 VTable에서 5번째 메서드 포인터 슬롯을 사용하게 된다. 그런데 원래 대로라면, 이 5번째 슬롯에는 클래스 A의 가상메서드 A.Run1() 포인터가 있어야 하는데, 여기서 이것이 실제 B.Run1()으로 대체되어 있다. 그리고 8번째 슬롯에 있을 것으로 추정되는 B 타입 메서드 B.Run1()은 존재하지 않는다...
이것이 C# virtual/override 키워드의 역활이다.
즉, Base클래스의 virtual 메서드를 파생클래스에서 override 하면 (1) 파생클래스의 VTable에 있는 Base클래스의 가상메서드 슬롯에 파생클래스 override 메서드의 포인터를 집어 넣고, (2) (override 메서드에 대한) 별도의 파생클래스 메서드 슬롯을 새로 만들지 않는 것이다. 그리고, 이것이 OOP의 Polymorphism을 가능하게 만드는 기본 메커니즘이다.
누군가 "타입 A" 변수로 메서드를 요구하면 Base 클래스 A의 메서드 범위에서 해당 (이미 overriding 된) 가상 메서드 포인터 를 리턴하고, "타입 B" 변수로 그 메서드를 요구하면, 파생클래스 메서드 슬롯에는 해당 메서드가 없으니 Base클래스의 가상메서드를 리턴하는데 실제 그곳에는 override 메서드를 가리키는 포인터가 있는 것이다.
'CS, 기술면접' 카테고리의 다른 글
객체 지향 프로그래밍(OOP)과 4특징 (0) | 2023.10.26 |
---|---|
박싱(Boxing), 언박싱(UnBoxing) (0) | 2023.10.24 |
값 형식(value) vs 참조 형식(reference) (0) | 2023.10.24 |
메모리 구조 (프로세스 메모리 영역) (0) | 2023.10.16 |
가비지 컬렉터 (Garbage Collector) (0) | 2023.10.13 |