18.3 The override and final specifiers, and covariant return types

주홍영·2022년 3월 21일
0

Learncpp.com

목록 보기
174/199

https://www.learncpp.com/cpp-tutorial/the-override-and-final-specifiers-and-covariant-return-types/

inheritance에 흔한 문제를 다루기 위해 두가지 특별한 identifiers가 있다
override와 final이다
참고로 이 두 identifier는 keyword는 아니다

비록 final은 자주 쓰이지 않지만 override는 우리가 적절히 사용한다면 많은 도움을 준다
이번 레슨에서는 우리는 두 identifier에 대해서 살펴볼 뿐만 아니라 virtual function이 return type이 일치해야 한다는 것의 반례에 대해서도 살펴본다

The override specifier

이전 레슨에서 말했듯 derived 클래스의 virtual function은 signature와 return type이 일치해야 override로 간주된다.
이는 부주의한 문제를 일으킬 수 있다. override를 의도했으나 그렇지 않았을 때

예시를 살펴보자

#include <iostream>
#include <string_view>

class A
{
public:
	virtual std::string_view getName1(int x) { return "A"; }
	virtual std::string_view getName2(int x) { return "A"; }
};

class B : public A
{
public:
	virtual std::string_view getName1(short int x) { return "B"; } // note: parameter is a short int
	virtual std::string_view getName2(int x) const { return "B"; } // note: function is const
};

int main()
{
	B b{};
	A& rBase{ b };
	std::cout << rBase.getName1(1) << '\n';
	std::cout << rBase.getName2(2) << '\n';

	return 0;
}

rBase가 A에 대한 reference이다. 그런데 getName1,2 모두 virtual function이다
그러나 B클래스에서 getName1이 parameter가 다른 형태이다. 즉, signatur가 다르다
그리고 getName2도 const로 declare 되어 있어 다른 signature를 가지므로
둘 다 override로 간주되지 않는다

따라서 출력은 다음과 같다

A
A

참고로 return type이 같고 이름이 같은데 parameter가 다른경우
function overloading으로 본다.

위와 같은 경우 override가 의도치 않게 된것으로 볼 수 있다
더 복잡한 프로그램에서는 이러한 문제를 debugging하는게 더 힘들어질 수 있다

이러한 문제를 다루기 위해 override identifier를 const 위치에 위치시켜주면
만약 funtcion이 base class의 function을 override를 하지 않았을 경우 error를 준다

#include <string_view>

class A
{
public:
	virtual std::string_view getName1(int x) { return "A"; }
	virtual std::string_view getName2(int x) { return "A"; }
	virtual std::string_view getName3(int x) { return "A"; }
};

class B : public A
{
public:
	std::string_view getName1(short int x) override { return "B"; } // compile error, function is not an override
	std::string_view getName2(int x) const override { return "B"; } // compile error, function is not an override
	std::string_view getName3(int x) override { return "B"; } // okay, function is an override of A::getName3(int)

};

int main()
{
	return 0;
}

위의 프로그램은 두 가지 컴파일 에러를 발생시킨다
getName1과 getName2는 override를 올바르게 하지 않고 있기 때문이다

override identifier는 performance 저하에 대한 문제 없이 function override에 대해서 보장할 수 있도록 프로그래머를 도와준다
추가적으로 override 라는 identifier가 virtual의 의미를 내포하고 있기 때문에 virtual을 생략해도 된다

The final specifier

클래스의 어떤 함수들은 더 이상 상속되거나 override가 되기를 원치 않는 경우가 있을 수 있다
final specifier는 컴파일러에게 이를 강제시킬 수 있다
만약 final인 함수를 override 하거나 상속받으려는 시도를 한다면 compile error가 발생한다

final specifier역시 override와 같은 곳에 위치한다

#include <string_view>

class A
{
public:
	virtual std::string_view getName() { return "A"; }
};

class B : public A
{
public:
	// note use of final specifier on following line -- that makes this function no longer overridable
	std::string_view getName() override final { return "B"; } // okay, overrides A::getName()
};

class C : public B
{
public:
	std::string_view getName() override { return "C"; } // compile error: overrides B::getName(), which is final
};

위에서 getName()이 final 함수이므로 C에서 override하려고 시도하면 compile error가 발생한다

만약 클래스 상속자체를 방지하기 위해서는 클래스 이름 뒤에 final을 할 수도 있다

#include <string_view>

class A
{
public:
	virtual std::string_view getName() { return "A"; }
};

class B final : public A // note use of final specifier here
{
public:
	std::string_view getName() override { return "B"; }
};

class C : public B // compile error: cannot inherit from final class
{
public:
	std::string_view getName() override { return "C"; }
};

class B final이라고 설정했음으로 class C는 B를 public 상속할 수가 없다

Covariant return types

derived 클래스의 virtual function이 다른 return type을 갖을 수 있는 특별한 case가 있다. 이때 override는 유지된 채로 말이다
만약 virtual function의 return type이 Base 클래스에 대한 pointer 혹은 reference인 경우에 override function은 derived 클래스의 pointer 혹은 reference로 변경할 수 있다
이때 return type을 covariant return types라고 한다
예시를 보자

#include <iostream>
#include <string_view>

class Base
{
public:
	// This version of getThis() returns a pointer to a Base class
	virtual Base* getThis() { std::cout << "called Base::getThis()\n"; return this; }
	void printType() { std::cout << "returned a Base\n"; }
};

class Derived : public Base
{
public:
	// Normally override functions have to return objects of the same type as the base function
	// However, because Derived is derived from Base, it's okay to return Derived* instead of Base*
	Derived* getThis() override { std::cout << "called Derived::getThis()\n";  return this; }
	void printType() { std::cout << "returned a Derived\n"; }
};

int main()
{
	Derived d{};
	Base* b{ &d };
	d.getThis()->printType(); // calls Derived::getThis(), returns a Derived*, calls Derived::printType
	b->getThis()->printType(); // calls Derived::getThis(), returns a Base*, calls Base::printType

	return 0;
}

getThis는 virtual이고 override인데 return type이 Base pointer 와 Derived pointer로 서로 다르다
그러나 여기서 override에 대한 문제가 발생하지는 않는다
위 프로그램을 실행시키면 출력은 다음과 같다

called Derived::getThis()
returned a Derived
called Derived::getThis()
returned a Base

여기서 주목할 케이스는 b의 경우이다
b에서 getThis를 호출하면 virtual로 인해 Derived::getThis()를 호출한다
그리고 여기서 this를 반환하는데 이때 this가 Base 타입이다
따라서 Derived
가 반환 값이지만 upacasting을 통해 Base pointer가 return 된다
따라서 print type에서 Base의 printType()이 호출된다

profile
청룡동거주민

0개의 댓글