layout: single title: “Access To Private Member in C++” date: 2022-11-16 15:30:09 +0900 categories: C++, Langugage toc: true toc_sticky: true
C++ 처럼 memory address 를 직접적으로 다룰 수 있는 언어는 강제적 형변환을 통해 private member 에 접근하면 된다. 하지만, 이런 방식은 어떤 면에서 위험하다. 예를 들면, 어떤 라이브러리에 정의된 Class 의 private member 에 접근하고자 한다면 그 라이브러리의 버전이 변경되는 등의 이유로 Class 의 구조가 변경될 수 있다. 그럴 때마다 offset 을 계산해서 내 코드도 변경해줘야한다. 뿐만 아니라 실제로 undefined behavior 로 불리는 행위임. target class 가 vtable 을 갖고 있는지 등의 이유로 offset 은 언제든 변할 수 있음.
litb 의 블로그에서 이런 문제점을 해결해주는 trick 을 소개하고 있다.
이 trick 을 이해하기 위해서는 friend keyword, member pointer, ADL 에 대해 알 필요가 있다. 하나하나 알아보도록 하자.
friend keyword 는 이 기능 자체로 private member 에 접근할 수 있도록 허용해주는 것임. 그러나 이 키워드는 내부에서 문을 열어주는 느낌임. 예를 들면, 아래 코드는 friend 를 통해서 A 의 private member access 권한을 x 함수에게 줬다.
Class A{
//private members
int x;
friend void x(A a); // open the door to function 'x'
}
void x(A a){
printf("%d\n",a.x);
}
library 에서는 이런 것을 아직 정의도 되지 않은 함수, class 에 대해 열어줄 리 만무하다.
:: 를 사용하면 우리는 클래스의 member 를 지칭할 수 있다. 예를 들면 아래 코드처럼 어떤 클래스(instance 가 아님) 의 멤버에 대한 refernece 를 변수(p) 에 담아 어떤 객체(instance) 의 멤버에 접근하는데 사용할 수 있다.
struct A {
A(int a):a(a) { }
int b;
private:
int a;
};
void test() {
auto p = &A::b;
std::cout << a.*p << std::endl;
}
그러면 이 방식을 바로 사용해서 private member 에 접근하면 안될까? 컴파일러에서 막는다.
error: ‘int A::a’ is private within this context
하지만, test 함수에 대해 friend 선언을 하면 접근이 가능하다.
Koenig lookup 라고도 불리는데, ADL 은 함수 호출 표현식에서 unqualified function name 을 찾는 일련의 규칙. 이름에서 알 수 있듯이, 기존의 scope 와 namesapce 에 더해 argument 의 namespace 를 참고해서 찾는다.
#include <iostream>
int main()
{
std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
// examines std namespace because the left argument is in
// std and finds std::operator<<(std::ostream&, const char*)
operator<<(std::cout, "Test\n"); // Same, using function call notation
// However,
std::cout << endl; // Error: “endl” is not declared in this namespace.
// This is not a function call to endl(), so ADL does not apply
endl(std::cout); // OK: this is a function call: ADL examines std namespace
// because the argument of endl is in std, and finds std::endl
(endl)(std::cout); // Error: “endl” is not declared in this namespace.
// The sub-expression (endl) is not an unqualified-id
}
endl 함수는 인자의 namespace 가 std::cout 이니 std::endl() 을 이용하게 된다.