클래스 멤버의 접근성 제어
C++에서는 클래스의 접근 지정자( access specifier )를 통해서, 외부나 상속받은 클래스에서 대상 클래스의 멤버에 접근할 수 있는지 여부를 제어할 수 있습니다.
class CBase{
private:
int m_private;
protected:
int m_protected;
public:
int m_public;
};
int main(){
CBase base;
base.m_private = 1; // error !
base.m_protected = 2; // error !
base.m_public = 3; // ok
}
클래스 외부에서 base
의 멤버에 접근하려면, 그 멤버는 public 접근 권한을 가져야 합니다.
그러므로, 위의 예처럼, m_private
와 m_protected
멤버에 접근하는 것은 권한의 위반이 됩니다.
그리고, 이러한 권한은 CBase
를 public 상속받은 파생 클래스에도 이어집니다.
class CDerived : public CBase{ // public 상속
public:
int add_one(){
return m_private + 1; // error !
}
};
int main(){
CDerived derived;
derived.m_private = 1; // error !
derived.m_protected = 2; // error !
derived.m_public = 3; // ok
}
그렇기 때문에, 이때도 이전 예문의 base
객체를 사용할 때와 마찬가지로, 외부에서 derived
객체의 m_private
와 m_protected
멤버엔 접근할 수 없습니다.
하지만, 파생 클래스에서 기반 클래스의 멤버에 접근할 때는, 외부에서 접근할 때와 약간 차이가 있습니다.
파생 클래스 CDerived
에선 기반 클래스에서 private 권한을 가진 멤버에는 접근을 할 수 없다는 점은 외부에서 접근할 때와 같지만, protected 권한의 멤버( 여기서는 m_protected
)에는 접근이 가능합니다.
파생 클래스에서 기본 클래스의 접근 권한 확대
그런데, 기본 클래스의 구성은 그대로 둔 채, 이 접근 권한을 상속받은 CDerived
클래스를 사용할 때만, 멤버에 접근성을 변경하고 싶을 때가 있습니다.
주로, 기존의 클래스를 재활용하려는 경우이거나, 외부 라이브러리를 사용하는 경우가 이러한 경우에 해당됩니다.
class CDerived : public CBase{ // public 상속
public:
using CBase::m_protected; // public 권한으로 변경
};
int main(){
CDerived derived;
derived.m_private = 1; // error !
derived.m_protected = 2; // ok !
derived.m_public = 3; // ok !
}
위와 같이, public 접근 지정자와 using 키워드를 함께 사용해서, CBase
의 protected 권한을 가진 m_protected
멤버의 권한을, CDerived
클래스에서는 public 권한을 가지도록 변경을 할 수가 있습니다.
그리고, 이러한 접근 권한은 데이터 멤버( 멤버 변수 )뿐만이 아니라 멤버 함수와 멤버 타입( type )에도 동일하게 적용되므로, 다음과 같은 코드도 가능합니다.
#include <iostream>
class CBase{
private:
int m_private;
protected:
int m_protected;
int getPrivate(){ // protected 멤버 함수
return m_private;
}
public:
int m_public;
};
class CDerived : public CBase{ // public 상속
public:
using CBase::getPrivate; // public 권한으로 변경
using CBase::m_private; // error ! private 멤버는 변경 불가
};
int main(){
CDerived derived;
int val = derived.getPrivate(); // getPrivate는 public 권한이 있음
std::cout << val << '\n';
}
여기서 주의할 것이 있는데, CDerived
클래스는 CBase
의 private 멤버에 접근할 수 없으므로, m_private
의 접근 권한은 변경할 수 없다는 것입니다.
파생 클래스에서 기본 클래스의 접근 권한 축소
접근 권한을 확대할 수 있는 것과 같이, 반대로 그 권한을 축소할 수도 있습니다.
class CDerived : public CBase{ // public 상속
private:
using CBase::m_protected; // private 권한으로 변경
protected:
using CBase::m_public; // protected 권한으로 변경
};
int main(){
CDerived derived;
derived.m_private = 1; // error !
derived.m_protected = 2; // error !
derived.m_public = 3; // error !
}
위 예문에선, private 접근 지정자와 using 키워드를 사용하여, m_protected
의 protected 권한을 private 권한으로 변경하고, m_public
의 public 권한은 CDerived
클래스에서 protected 권한으로 변경하였습니다.
따라서, 외부에서는 derived
객체의 모든 데이터 멤버에 접근할 수 없습니다.
● 그리고, 이렇게 기본 클래스 멤버의 접근 권한을 축소하는 방법은, 클래스 상속 방법의 변경을 통해서도 실행할 수 있습니다.
class CDerived : private CBase{ // private 상속
};
int main(){
CDerived derived;
derived.m_private = 1; // error !
derived.m_protected = 2; // error !
derived.m_public = 3; // error !
}
위와 같이, CBase
를 private 상속받게 되면, CBase
의 모든 멤버들을 private 권한을 가진 상태로 상속받게 됩니다.
따라서, 위와 같이 외부( 그리고, CDerived
를 다시 상속받은 파생 클래스 )에서, CDerived
의 멤버에 접근할 수 없습니다.
● 만약, CBase
의 접근을 제한하고 싶은 멤버가 함수라면, 함수를 삭제하는 방법을 통해서 접근하는 방법을 막을 수도 있습니다.
#include <iostream>
class CBase{
private:
int m_private;
protected:
int m_protected;
public:
int m_public;
int getPrivate(){ return m_private; }
};
class CDerived : public CBase{ // public 상속
int getPrivate() = delete; // 함수 삭제
};
int main(){
CDerived derived;
int val = derived.getPrivate();
std::cout << val << '\n';
}
▼출력
error: use of deleted function 'int CDerived::getPrivate()'
파생 클래스의 접근 권한 제어의 주의사항
지금까지 얘기해 왔던 접근 권한 제어 방법은 파생 클래스를 사용하는 입장에서 쓸 수 있는 방법입니다.
즉, 기반 클래스를 사용하는 경우, 이와 같은 방법은 전혀 영향을 끼칠 수 없다는 것입니다.
#include <iostream>
class CBase{
private:
int m_private;
protected:
int m_protected;
public:
int m_public;
int getPrivate() const { return m_private; } // public 접근 권한
};
class CDerived : public CBase{ // public 상속
private:
using CBase::getPrivate; // private 권한으로 변경
};
void printPrivateMember( const CBase& base){
int val = base.getPrivate();
std::cout << val << '\n';
}
int main(){
CDerived derived;
int val = derived.getPrivate(); // error !! 접근할 수 없음
std::cout << val << '\n';
printPrivateMember( derived); // ok
}
위의 코드를 보면, CDerived
클래스 입장에서 보면, getPrivate 함수는 private 권한을 갖고 있습니다.
그래서, derived.getPrivate()
함수 호출은 오류입니다.
하지만, CBase
클래스 입장에서 보면, getPrivate 함수는 public 접근 권한을 갖고 있습니다.
따라서, printPrivateMember 함수 호출은 제대로 동작하는 것을 볼 수 있습니다.
그리고 이것이 당연한 것은, 상속받은 클래스에서 권한을 변경한다고 해서, 기반 클래스를 이용한 모든 코드가 변경되어야 하는 일이 생기는 것은 말이 안 되기 때문입니다.
그래서, 변경된 권한에 따라 코드가 작성되기 원한다면, printPrivateMember 함수를 다음과 같이 수정해야 합니다.
void printPrivateMember( const CDerived& derived){
int val = derived.getPrivate(); // 접근할 수 없음, error !
std::cout << val << '\n';
}
▼출력
error: 'int CBase::getPrivate() const' is inaccessible within this context
그리고, 이와 비슷한 일은 가상 함수( virtual function )를 사용할 때도 발생합니다.
#include <iostream>
class CBase{
protected:
int m_protected = 10;
public:
virtual int getProtected() const { return m_protected; } // public 권한
};
class CDerived : public CBase{ // public 상속
private:
virtual int getProtected() const { return m_protected; }; // private 권한
};
void printPrivateMember( const CBase& base){
int val = base.getProtected(); // 가상 함수 호출
std::cout << val << '\n';
}
int main(){
CDerived derived;
int val = derived.getProtected(); // error !! 접근할 수 없음
std::cout << val << '\n';
printPrivateMember( derived); // ok
}
이 예문도 이전 경우와 같이, CDerived
클래스의 getProtected 함수를 호출할 수 없습니다.( private 접근 권한입니다. )
그렇지만, CBase
클래스의 public 권한을 가진 getProtected 함수를 호출하는데 아무런 지장이 없습니다.
혹시, getProtected 함수는 가상 함수이기 때문에, base.getProtected()
함수 호출은, private 권한의 CDerived
의 getProtected 함수를 호출하기 때문에 오류가 맞지 않는가라고 생각할 수 있습니다.
하지만, CDerived
의 getProtected 함수 호출을 결정하는 시기는 실시간이고, 접근 권한을 확인하는 시기는 컴파일을 수행할 때입니다.
그렇기 때문에, printPrivateMember 함수는 제대로 컴파일되고, 동작합니다.
정리
- 접근 지정자( access specifier )와 using 키워드를 통해, 파생 클래스에서 상속받은 멤버들의 접근 권한을 제어할 수 있습니다.
- 파생 클래스의 접근 권한 변경은 기반( base ) 클래스의 동작에 영향을 끼칠 수 없습니다.