const 멤버함수로 변경시 조심점
virtual 멤버함수에 함부로 const를 붙였다가 사고가 날 수도 있다.
1
2
3
4
5
6
7
8
9
const A *a = get();
a->func(); // Compile Error !
class A
{
public:
virtual func(){}
}
해당 로직에서 코드를 작성하다 A 클래스의 포인터를 const 타입으로 받아오는 식으로 작성하였는데 이후에 사용하는 func 함수가 const가 빠져있어, const 타입으로 사용이 불가능했다. 해당 함수는 함수 목적상 내부값을 변경하지않는 함수라 const가 빠져있을 필요가 없어서, 함수에 const를 같이 붙여주게되었다.
대충 이런 느낌으로 수정
1
2
3
4
5
class A
{
public:
virtual func() const {}
}
문제는 이러고 코드를 작성하고 테스트하는데 완전 엉뚱한 곳에서 크래시가 발생는데, 내가 작성한 로직까지 도달하지도 못하고 기존 로직에서 이상하게 크래시가 발생했다.
꽤나 잡기 힘든 버그였다.
여기서 잠시 멈추고 위의 정보만으로 원인을 파악할 수 있을지 한번 생각해보자.
면접문제로도 굉장히 괜찮은 것 같다.
본문엔 문제가 될 케이스만 콕집어 올렸지만, 나같은 경우는 코드 작업중에 곁다리로 작업한 것 중 하나였다. (고작 기존 함수 하나 const 붙인것 뿐이니까)
작성한 코드량이 그렇게 크지도 않았는데, 작업 영역별로 다 롤백을 하면서 크래시 유무를 체크했고, 꼬박 반나절이 걸렸다.
롤백을 하면서 당연히 const 붙었다고 크래시가 날 줄은 생각도 못했어서 마지막의 마지막에 가서 발견을 했기 때문.
물론 const가 문제가 확실하게 알아차린 시점에선 바로 발생 원인을 이해했다.
virtual 함수가 붙어있는 클래스답게 사실 A 클래스는 상속을 하는 클래스였기 때문에, 아래와 같은 구조로 잡혀있었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A
{
public:
virtual func(){}
}
class B : public A
{
public:
virtual func(){}
}
class C : public A
{
public:
virtual func(){}
}
여기에 const를 추가하여 다음과같이 구조가 잡혔다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A
{
public:
virtual func() const {}
}
class B : public A
{
public:
virtual func(){}
}
class C : public A
{
public:
virtual func(){}
}
자 B 클래스와 C 클래스의 func는 A의 func를 오버라이딩한 함수인가?
정답은 아니오다.
const 멤버함수와 비 const 멤버함수는 별개의 함수이다. 위의 클래스구조에서 상속을 받는 함수는 없고 모두 개별 함수이다.
위처럼 구조가 잡히면 실제 가상함수 func 이 호출되어야하는 모든 곳에서 A func만 호출이 된다.
1
a->func() // A::func만 가상함수 테이블을 통하지않은 call instruction으로 호출
c++의 오버로딩과 같은 골자다.
로컬에서 코드를 개발하고 테스트하는 와중에 발생해서 알아차렸지, 구현부에 따라서 충분히 모르고 넘어갈 수 있었던 케이스였고, 이럴 경우 릴리즈에서 심각한 버그로 발생했을 사안이였다. 코드 리뷰에서조차 놓쳤을 수도 있었을부분
어떻게하면 이런 문제를 방지하고 고칠 수 있을까를 따져보면 이정도가 생각나는데
상속받는 함수에는 override 키워드 붙이기
팀의 default 룰로서 정해져있으면 가장 좋다. 위의 같은 문제를 근본적으로 컴파일에러로 지켜준다.
초기 함수 작성자가 할 수 있는 방지책
사실 이걸 붙일정도의 섬세함이라면 const 키워드는 이미 붙어져 있었을거다.
const를 붙일때, virtual인 함수인지 체크
const를 붙이는 사람 입장에서 가져야하는 경각심
사실 코드 잘못건들다 문제터지면 결국 본인 잘못이니 이런 놓친부분에서 생긴 문제는 본인만 구박받지, const붙인다고 아무도 칭찬안하지만 그럼에도 붙이고 싶다면 가상함수인지 체크를 하면서 붙이자.
오버로딩을 언어단에서 막기.
사실 C언어는 오버로딩 자체가 존재하지않는다.
C언어같이 함수 오버로딩이 불가능하다면, 생기지않을 문제이다. (이경우 override할 함수에 오타가 난것까지 체크하고 막아주진않는다.) C언어같이 함수자체를 막는 방법도있는데 조금 제한을 넓힌다면, 타입에 따른 오버로딩을 문법적으로 막고, namespace,클래스 수준에서 네이밍이 동일한 함수정도만 허용해줘도 충분하지않을까 싶다.