C# 내용도 포함되어 있습니다.
학생 때는 C++을 주로 사용했지만 현재는 Unity 개발자로서 C#을 더 많이 사용하고 있다.
그러다보니 C++을 공부하다 보면 간혹 “이게 안되네..?” 하는 것들이 있는데 그 중 하나가 자식 클래스에서 부모 클래스 함수 사용하기 이다.
왜 그렇게 생각했는지 C# 먼저 간단하게 살펴보자.
[C#과 C++ 비교] 자식 객체에서 부모 클래스 함수 사용하기
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// See https://aka.ms/new-console-template for more information
Derived d = new Derived();
Console.WriteLine("===============");
d.Func(5);
d.Func("기본 클래스로");
class Base
{
public Base()
{
Console.WriteLine("Base 생성자");
}
public void Func(string str)
{
Console.WriteLine($"Base::Func(string str) -> str is \"{str}\"");
}
}
class Derived : Base
{
public Derived()
{
Console.WriteLine("Derived 생성자");
}
public void Func(int n)
{
Console.WriteLine($"Derived::Func(int n) -> n is {n}");
}
}
C#에서는 Derived d = new Dervied()
를 통한 파생 클래스 인스턴스로 Derived::Func(int n)
과 Base::Func(string str)
둘 다 호출할 수 있는 것을 볼 수 있다.
그럼 C++에서는 어떻게 될까?
C++
위 C#과 동일하게 C++코드를 구현해보았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<iostream>
#include<string>
class Base
{
public:
Base()
{
using namespace std;
cout << "Base 생성자" << endl;
}
void Func(std::string str)
{
using namespace std;
cout << "Base::Func(string str) -> str is \""<< str << "\"" << endl;
}
};
class Derived : public Base
{
public:
Derived() : Base()
{
std::cout << "Derived 생성자" << std::endl;
}
void Func(int n)
{
using namespace std;
cout << "Derived::Func(int n) -> n is" << n << endl;
}
};
int main()
{
Derived d;
std::cout << "=========" << std::endl;
d.Func(5);
d.Func("기본 클래스로"); // Complie Error! - "const char *" 형식의 인수가 "int" 형식의 매개 변수와 호환되지 않습니다.
}
C++에서는 자식(파생) 클래스 인스턴스로 부모(기본) 클래스에 있는 Base::Func(std::string str)
함수 호출이 되지 않고 컴파일 에러가 발생하는 것을 볼 수 있다.
필자의 경우, C#을 하다 C++ 하다 그러다 보니 이런 차이점이 너무 혼란스러웠다.
그럼 C++에서는 어떻게 해야 자식 인스턴스에서 부모 클래스 함수를 호출할 수 있을까??
예시를 통해 하나씩 알아보자.
1. 자식 클래스 함수에서 부모 클래스 함수를 호출하기
매우 당연한 방법이라 추가 설명 없이 코드만 첨부하였다.
가상 함수로 구현해도 되지만 예시를 위해 사용하지 않았습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Base
{
public:
Base()
{
using namespace std;
cout << "Base 생성자" << endl;
}
void Func(std::string str)
{
using namespace std;
cout << "Base::Func(string str) -> str is \""<< str << "\"" << endl;
}
};
class Derived : public Base
{
public:
Derived() : Base()
{
std::cout << "Derived 생성자" << std::endl;
}
void Func(int n)
{
using namespace std;
cout << "Derived::Func(int n) -> n is" << n << endl;
}
// 자식 클래스 멤버 함수에서 부모 클래스를 호출하였다.
void Func(std::string str)
{
Base::Func(str);
}
};
int main()
{
Derived d;
std::cout << "=========" << std::endl;
d.Func(5);
d.Func("기본 클래스로");
}
실행 결과
1
2
3
4
5
Base 생성자
Derived 생성자
=========
Derived::Func(int n) -> n is5
Base::Func(string str) -> str is "기본 클래스로"
2. using 선언문 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
#include<string>
class Base
{
public:
Base()
{
using namespace std;
cout << "Base 생성자" << endl;
}
void Func(std::string str)
{
using namespace std;
cout << "Base::Func(string str) -> str is \""<< str << "\"" << endl;
}
};
class Derived : public Base
{
public:
// using 선언문 사용
using Base::Func;
Derived() : Base()
{
std::cout << "Derived 생성자" << std::endl;
}
void Func(int n)
{
using namespace std;
cout << "Derived::Func(int n) -> n is" << n << endl;
}
};
int main()
{
Derived d;
std::cout << "=========" << std::endl;
d.Func(5);
d.Func("기본 클래스로");
}
실행 결과
1
2
3
4
5
Base 생성자
Derived 생성자
=========
Derived::Func(int n) -> n is5
Base::Func(string str) -> str is "기본 클래스로"
위 코드를 보면 using Base::Func;
가 추가된 것을 볼 수 있다.
C++에서 이런 문법을 using 선언문
이라고 하는데 말 그대로 Base::Func
를 Derived
클래스에 선언시키는 것이고
선언을 통해 Derived
객체에서도 Base::Func
함수의 존재를 알고 사용할 수 있는 것이다.
가상 함수를 만드는 것인가?
C++ MSDN - 선언 사용 글에서 보면 위 내용을 아래와 같이 설명하고 있다.
상속과 관련하여 using 선언이 기본 클래스의 이름을 파생 클래스 scope 도입하는 경우 파생 클래스의 멤버 함수는 기본 클래스의 이름과 인수 형식이 같은 가상 멤버 함수를 재정의합니다.
제대로 이해한 건지는 모르겠지만 가상 멤버 함수를 재정의한다라는 말이 있어서 using 선언문 사용 시 vptr
이 생성되는 것인지 확인하고 싶어졌고 테스트를 진행했다.
가상 함수가 있을 때 vptr
확인해보기
테스트를 하기 전에 가상 함수가 있을 때 vptr
이 어떻게 생성되는지 확인해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Derived : public Base
{
public:
using Base::Func;
Derived() : Base()
{
std::cout << "Derived 생성자" << std::endl;
}
// 테스트용으로 가상 함수를 추가
virtual void T()
{
}
void Func(int n)
{
using namespace std;
cout << "Derived::Func(int n) -> n is" << n << endl;
}
};
Dervied
생성자에 중단점을 찍고 디버깅을 해보면 위처럼 vptr
이 생성된 것을 볼 수 있다.
using 선언문은 vptr
이 있을까
이제 진짜로 테스트를 진행해보자.
기존 using 선언문
이 있던 코드에서 위와 동일하게 Dervied
생성자에 중단점을 찍어 확인해보았다.
Dervied
에는 vptr
이 없는 것을 확인할 수 있다.
혹시 Base
클래스에 생성되는 것이 아닌가 싶어서 Base
클래스도 확인해보았는데 아래와 같이 Base
클래스에도 vptr
이 없는 것을 볼 수 있다.
접근 지정자(private, protected, public) 영향을 받을까?
Base::Func(std::string)
을 private
으로 바꿔보면 액세스 할 수 없다는 컴파일 에러가 발생한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
private:
void Func(std::string str)
{
using namespace std;
cout << "Base::Func(string str) -> str is \"" << str << "\"" << endl;
}
public:
Base()
{
using namespace std;
cout << "Base 생성자" << endl;
}
};
에러 메시지를 통해 알 수 있듯이, protected
는 당연히 문제 없이 컴파일 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
protected:
void Func(std::string str)
{
using namespace std;
cout << "[protected] Base::Func(string str) -> str is \"" << str << "\"" << endl;
}
public:
Base()
{
using namespace std;
cout << "Base 생성자" << endl;
}
};
실행 결과
1
2
3
4
5
Base 생성자
Derived 생성자
=========
Derived::Func(int n) -> n is5
[protected] Base::Func(string str) -> str is "기본 클래스로"
3. 인스턴스에서 명시적으로 접근하기
C++에서 권장하는 방식인지는 모르겠으나 “이것도 되지 않을까..?”싶었는데 문제 없이 출력되어서 추가로 작성하였다.
하는 방식은 자식 객체에서 부모 클래스 함수를 명시적으로 호출하기인데 코드를 보면 쉽게 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
#include<string>
class Base
{
public:
Base()
{
using namespace std;
cout << "Base 생성자" << endl;
}
void Func(std::string str)
{
using namespace std;
cout << "Base::Func(string str) -> str is \"" << str << "\"" << endl;
}
};
class Derived : public Base
{
public:
Derived() : Base()
{
std::cout << "Derived 생성자" << std::endl;
}
void Func(int n)
{
using namespace std;
cout << "Derived::Func(int n) -> n is" << n << endl;
}
};
int main()
{
Derived d;
std::cout << "=========" << std::endl;
d.Func(5);
// 자식 클래스 객체에서 Base::Func 을 명시적으로 호출하였다.
d.Base::Func("기본 클래스로");
}
실행 결과
1
2
3
4
5
Base 생성자
Derived 생성자
=========
Derived::Func(int n) -> n is5
Base::Func(string str) -> str is "기본 클래스로"
위 코드에서 using 선언문을 사용하지 않았음을 확인할 수 있다.
main()을 보면 d.Base::Func("기본 클래스로");
방식으로 자식 클래스 객체를 통해 부모 클래스 함수를 명시적으로 호출하였다.
using 선언문
과 가장 큰 차이점은 public
멤버 함수만 접근이 가능하다`이다.
클래스 외부에서 접근한 것으로 인지하기 때문에
public
만 가능한 것으로 보인다.
참고 자료
C++ MSDN - 선언 사용