2013년 11월 12일 화요일

[C++] C++의 난감한 상황...

C++의 private 접근제어 키워드에 대하여 이야기를 한 바 있다.
이번에는 어찌 보면 좀 황당한(?) 소스코드를 이야기하고자 한다.

참고로 지금 언급하고자 하는 개념은, 어떤 블로그에서는 당당하게 "C++ 클래스의 private 멤버 변수에 접근하고 값을 변경하는 방법!!" 이라고 소개되기도 하였다. (개인적으로 참 난감한 글이었다.)

아래 소스코드를 보자.

#include <iostream>

class TempClass
{
private:
    const int a;
    const int b;
public:
    TempClass() : a(100), b(200) {}
    TempClass(int i, int j) : a(i), b(j) {}
    int get_a() const { return a; }
    int get_b() const { return b; }
};

int main()
{
    TempClass c;

    int &aa = *reinterpret_cast<int*>(&c);      // int &aa = *(int*)(&c);
    int &bb = *(reinterpret_cast<int*>(&c)+1);  // int &bb = *((int*)(&c)+1);
    std::cout << "before c.a : " << c.get_a() << "   c.b : " << c.get_b() << std::endl;
    aa = 300;
    bb = 400;
    std::cout << "after  c.a : " << c.get_a() << "   c.b : " << c.get_b() << std::endl;

    return 0;
}

일반적인 class인 TempClass를 정의하였고, private 멤버변수 a, b는 const 이므로 생성자에 의해서만 값이 초기화된다.

그런데 main 함수에서 생성된 클래스 인스턴스의 주소를 받았고 이를 이용하여 reinterpret_cast 를 써서 "억지로" private 멤버에 접근을 하여 참조자를 부여하였다.
(주석처리된 C 스타일의 casting 또한 결국 동일한 의미이다.)

결론은... 역시 정상적으로 컴파일되며 값 또한 변경이 된다.

....................

C, C++ (뿐만 아니라 어느 언어로 프로그래밍을 하든 비슷한 이야기지만) 프로그래밍에서 사용되는 키워드의 의미를 망각하거나 잘못 이해하면 안되겠다.

C, C++ 에서 사용되는 키워드인 const, private 등은 어디까지나 프로그래머를 위한 키워드이지 프로그램을 사용하는 사용자를 위한 키워드가 아니다. 즉, 프로그래머가 위와 같은 키워드를 적절히 사용하여 프로그래밍을 하면, 나중에 그 소스코드를 이용할 때, 혹은 다른 사람이 그 소스코드를 활용할 때 이러한 키워드를 보고 해당 변수, 함수, 객체의 의미를 잘 정립할 수 있으며, 또한 혹시 모를 프로그램상의 오류를 미연에 방지할 수 있다는 데에 의미를 두어야 한다.

결국, 위와 같이 소스코드를 짜 보는 것은 프로그래머의 자유이고, 분명히 한번 궁금해서 실험적으로 해 볼 수 있는 시도임엔 틀림 없다. 무언가를 제한하는 키워드를 사용하는 의의를 역설적으로 음미해 보는 데에는 데에는 분명 유익한 코드이니까.

실전에서 저런 식으로 프로그래밍을 하는 것 만큼 어리석은 짓은 없을 것이다.

저것은 결코 대단한 기술도 아니며, 저걸 "private 변수에 접근할 수 있는 비법" 으로써 안다고 자랑할 만한 거리는 더더욱 아닌 것이다.

마치 뭐랄까, 박물관에 전시된 골동품에 "손대지 마시오!" 라고 적혀진 경고문구를 무시하고 자기 손때를 묻힌 후 만졌다고 자랑하는 거나 다를게 없어보인다는....

적어도 C/C++ 프로그래밍을 할때 const 와 private 등을 제대로 사용할 줄 아는 사람들에게는 저 코드는 정말 stupid한 소스코드이기에.

### Wafting ....... Done !!!
### ...
### ;;

[C++] 클래스의 private 접근지정자에 대한 고찰

이번엔 C++ 에 대한 글이다.
C++는 객체지향을 도입한 언어로서, 클래스를 정의할 수 있고 클래스에 대한 멤버 변수에 접근지정자를 부여할 수 있다. (private, protected, public)

private, protected, public의 의미는 알고 있다는 가정하에 이곳에서는 설명을 생략한다.

본론으로 바로 들어가서, 다음의 소스코드를 보면...

#include <iostream>

class Point
{
private:
    int x_;
    int y_;
public:
    Point() : x_(0), y_(0) {}
    Point(int x, int y) : x_(x), y_(y) {}
    Point(const Point &ref) : x_(ref.x_), y_(ref.y_) {}
    ~Point() {}

    void add_this(Point &ref)
    {
        x_ += ref.x_;
        y_ += ref.y_;
    }

    static Point add_new(Point &ref_a, Point &ref_b)
    {
        Point result;
        result.x_ = ref_a.x_ + ref_b.x_;
        result.y_ = ref_a.y_ + ref_b.y_;
        return result;
    }

    void show( const char *str )
    {
        std::cout << str << " : x[" << x_ << "] y[" << y_ << "]" << std::endl;
    }
};

int main()
{
    Point p1(100, 200);
    Point p2(300, 400);

    p1.show("p1");
    p2.show("p2");
    std::cout << std::endl;

    p1.add_this(p2);
    p1.show("p1");
    std::cout << std::endl;

    Point p3 = Point::add_new(p1, p2);
    p3.show("p3");
    std::cout << std::endl;

    return 0;
}

Point 클래스의 멤버 변수인 x_, y_ 는 private 변수이며, 자신의 멤버 함수들에 의해 접근하는 것은 당연히 허용한다. 그런데 문제는 멤버 함수 내부에서 다른 Point 클래스 객체를 생성하고 그 생성한 객체의 private 멤버 변수에 접근이 가능한지, 또는 멤버함수에서 다른 Point 클래스를 참조자로 받아 그 참조자로서 private 멤버 변수에 접근이 가능한지가 헷갈린다는 것이다.

위의 소스를 보면 add_new 함수는 자체적으로 Point 의 인스턴스를 생성한 후 그 인스턴스의 private 멤버변수에 바로 접근한다. 이 것이 컴파일이 가능하며 실행도 제대로 될까?

이상할 지도 모르지만, 위 소스는 아무런 문제 없이 컴파일 및 실행이 된다. 실행 결과는 다음과 같다.

p1 : x[100] y[200]
p2 : x[300] y[400]

p1 : x[400] y[600]

p3 : x[700] y[1000]

p3 클래스가 아무런 문제 없이 p1, p2의 각 멤버 변수의 합이 저장된 인스턴스로 생성이 되었다.

여기서 알 수 있는 사실은, 우리가 사용하는 접근제어자의 접근 제어 기준은 그때 그때 생성된 인스턴스의 기준이 아니라 클래스 기준이라는 것이다. 즉, 서로 다른 인스턴스이더라도 그 인스턴스가 동일한 클래스 타입이면 각 멤버는 서로의 private 멤버 변수에 접근이 가능하다는 이야기가 된다.

이것이 무언가 대단한 이야기 같지만, 가만히 생각해보면 당연한 이야기이다. C++에서 사용할 수 있는 friend 키워드도 그때그때 생성되는 인스턴스 기준이 아닌 틀 (클래스, 혹은 함수) 기준이다. 상속 또한 인스턴스가 아닌 클래스 기준이다.

다른 관점에서 접근하더라도 마찬가지이다. 만일 private 접근지정자가 클래스가 아닌 인스턴스 기준이라면 C++에서 기본적으로 제공해 주는(그래고 사용자 정의도 가능한) 복사생성자와 대부분의 연산자 오버로딩은 애초에 존재할 수가 없었을 것이다. ^^

뭔가 private이 private 같지 않아보일 지 모르지만, 이 사실은 우리가 private 에 대하여 좀 더 정확한 개념을 짚고 넘어가는 데에 도움이 될 것이다.

그렇다면 C++보다 객체지향성이 더욱 강한 JAVA는 어떨까?
결론부터 말하면... JAVA도 마찬가지이다. 아래 소스를 보자.

class A {
    private int val;
    public A() {
        this.val = 12345678;
    }
    public A(int i) {
        this.val = i;
    }
    
    public void test() {
        A a = new A();
        a.val = this.val;
        System.out.println("val a = " + a.val);
    }
    
    public void show() {
        System.out.println("val = " + this.val);
    }
}

public class Test {
    public static void main( String[] arags ) {
        System.out.println("Test !!!");
        A mainA = new A(87654321);
        mainA.show();
        mainA.test();
    }
    
}

class A 의 멤버 함수 test 안에서 다른 인스턴스 a를 선언한 후 a의 private 멤버 val에 직접 접근하여 값을 변경한다.
역시 정상 작동하며 결과는 다음과 같다.

Test !!!
val = 87654321
val a = 87654321

물론 JAVA에서 저런 형태의 소스코드를 작성할 경우가 드물 수도 있지만, private의 대상 범주를 좀 더 확실히 하는 데에 도움이 될 만한 소스코드일 것 같다.

결론은... 이 한마디로 갈음할 수 있을 듯 하다.

C++의 클래스는 자기 자신을 friend 클래스로 간주한다.

### Wafting ....... Done !!!
### ...
### ;;