D3로 시계 만들기

“소프트웨어 개발이 좋은 사람”이라는 블로그에서 d3를 이용해 아날로그 시계를 만든 글을 보고 코드를 살펴 보았습니다. d3는 개인적으로도 관심이 많고, 상당이 깊이 있게 공부를 했던(하지만 활용력은 아직까지도 얇은..) 자바스크립트 기반의 데이터 시각화 라이브러리입니다. 그 결과는 아래와 같습니다.

[xyz-ihs snippet=”D3-Clock”]

위의 시계에 대한 코드를 d3의 학습겸 해서 정리해 보고자 합니다. 먼저 시계에 대한 뷰는 아래처럼 오직 svg 태그 하나입니다.


시계의 크기에 해당하는 이 svg의 크기를 지정하기 위해 다음처럼 스타일을 지정합니다.

#clock {
    width: 400px;
    height: 400px;
}

시계의 구성요소는 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분으로 구성됩니다. 시분초에 대한 바늘은 Timer를 사용해 1초마다 갱신해 진짜 시계처럼 움직이도록 합니다.

스크립트 코드를 작성해 볼 것인데요. 가장 먼저 앞서 추가해둔 svg 요소를 d3의 선택자를 이용해 선택해 둡니다.

var clock = d3.select("#clock");

var center = parseInt(clock.style("height")) / 2;
var radius = center;

위의 코드는 추가적으로 svg의 높이값의 반값으로 중심점을 위한 center 변수와 시계의 반지름 크기값을 위한 radius 변수도 정의되어 있습니다.

앞서 시계는 3개의 부분으로 구성된다고 하였는데요. 다시 언급하면 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분입니다. 먼저 시계의 형태인 큰 동그라미 부분을 그리는 함수 drawFace를 반지름값(radius 변수)을 인자로 해 호출합니다.

drawFace(radius);

function drawFace(radius) {
    clock.append("circle")
        .attr("cx", center)
        .attr("cy", center)
        .attr("r", radius)
        .attr("class", "face");

    clock.append("circle")
        .attr({ 
            "cx": center, 
            "cy": center, 
            "r": radius * 0.1, 
            "fill": "#000" 
        });
}

위의 코드에서 face라는 class를 통해 스타일을 지정하고 있는데요. 이 .face에 대한 스타일 정의는 아래와 같습니다.

.face {
    fill : #FFF;
    stroke-width: 2px;
    stroke: #000;
}

또 아래는 12개의 숫자 부분을 구성하는 코드입니다.

drawNumbers(radius);

function drawNumbers(radius) {
    var pos = radius * 0.85;
    for (var num = 1; num < 13; num++) {
        var deg = 30 * num;
        var x = pos * Math.cos(Math.PI * (deg - 90) / 180);
        var y = pos * Math.sin(Math.PI * (deg - 90) / 180);
        var cx = x + center;
        var cy = y + center;
        var text = clock.append("text")
            .attr({ "x": cx, "y": cy, "class": "number" })
            .text(num)
            .style("font-size", radius * 0.15 + "px")
            .attr("transform", "rotate(" + deg + ", " + cx + ", " + cy + ")");
    }
}

위의 코드에서 number라는 class를 통해 숫자의 문자 스타일을 지정하고 있는데요. 이 .number에 대한 스타일 정의는 아래와 같습니다.

.number {
    font-family: arial;
    text-anchor: middle;
    text-align: center;
}

또 아래는 시, 분, 초에 대한 바늘을 구성하는 코드입니다. 시침, 분침, 초침을 각각 hourHand, minuteHand, secondHand 변수에 저장하고 있는데요. 이 변수를 통해 1초마다 각 침의 형태를 변경해 주게 됩니다.

var hourHand = drawHand(0, 0, radius*0.07);
var minuteHand = drawHand(0, 0, radius*0.05);
var secondHand = drawHand(0, 0, radius*0.01);

function drawHand(x, y, width) {
    var hand = clock.append("line")
        .attr({ 
            "x1": center, 
            "y1": center, 
            "x2": x + center, 
            "y2": y + center, 
            "class": "hands" })
        .style("stroke-width", width);

    return hand;
}

위의 코드에서 hand라는 class를 통해 바늘의 스타일을 정의하고 있습니다. 이 .hand에 대한 스타일 정의는 아래와 같습니다.

.hands {
    stroke: #000;
    stroke-linecap: round;
}

이제 1초마다 제 시간에 맞게 바늘을 움직도록 하는 코드는 아래와 같습니다.

drawClock();

function drawClock() {
    drawTime(radius);
    setTimeout(drawClock, 1000);
}

위의 코드에서 1초마다 실행되는 drawTime의 함수가 보이는데요. 바로 이 drawTime이 3개의 바늘을 현재 시간에 맞게 그 형상을 변경해 주는 함수이며 아래와 같습니다.

function drawTime(radius){
    var now = new Date();
    var hour = now.getHours();
    var minute = now.getMinutes();
    var second = now.getSeconds();
	
    //hour
    var pos = radius * 0.5;
    x = pos * Math.cos(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180); 
    y = pos * Math.sin(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180);
    hourHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});

    //minute
    pos = radius*0.65;
    x = pos * Math.cos(Math.PI*((minute*6)-90+6/60*second)/180); 
    y = pos * Math.sin(Math.PI*((minute*6)-90+6/60*second)/180);
    minuteHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
	
    // second
    pos = radius*0.9;
    x = pos * Math.cos(Math.PI* ((second*6)-90)/180); 
    y = pos * Math.sin(Math.PI* ((second*6)-90)/180);
    secondHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
}

이상으로 d3를 이용해 svg 요소를 시계로 만들어 보는 코드를 살펴보았습니다.

[Java] 우선순위 큐(Priority Queue) 활용 예제코드

자바에서 제공하는 컨테이너(Container) 중 어떤 데이터에 대해 우선순위 값을 부여하고, 이 우선순위를 기준으로 자동으로 정렬되어, 우선순위에 따라 데이터를 꺼내어 사용할 수 있는 우선순위 큐에 대한 예제 코드를 정리합니다.

먼저 우선순위 값을 갖는 데이터에 대한 타입 정의가 필요합니다. 아래처럼 Node라는 클래스를 추가해 타입을 정의합니다.

package tstPriorityQueue;

public class Node implements Comparable<Node> {
	private String UUID;
	private String parentUUID;
	private double G;
	private double H;
	
	public Node(String UUID, double G, double H) {
		this.UUID = UUID;
		this.parentUUID = null;
		this.G = G;
		this.H = H;
	}
	
	public double getF() { return G + H; }
	public double getG() { return G; }
	public double getH() { return H; }
	public String getNode() { return UUID; }
	public String getParentNode() { return parentUUID; }
	
	public void setG(double v) { G = v; }
	public void setH(double v) { H = v; }
	public void setParentNode(String v) { parentUUID = v; }
	
	@Override
	public int compareTo(Node target) {
	    if (this.getF() > target.getF()) {
            return 1;
        } else if (this.getF() < target.getF()) {
            return -1;
        }

	    return 0;
	}
	
	public String toString() {
		return UUID + '(' + getF() + ')';
	}
}

위의 클래스에서 중요한 부분은 우선순위값을 얻기 위한 getF() 함수입니다. 이 함수는 데이터의 상대적인 크기의 비교를 위한 인터페이스인 Comparable 구현할 때 사용되는 함수인데요. 바로 compareTo 라는 함수로써, 위의 경우에는 우선순위값이 작은 것을 먼저 꺼내어 사용하겠다는 정의입니다.

실제로, 위의 Node 클래스에 대한 타입으로 정의된 데이터를 컨테이너에 넣고, 사용하는 코드는 아래와 같습니다.

package tstPriorityQueue;

import java.util.PriorityQueue;

public class EntryMain {

	public static void main(String[] args) {
		// Create items
		Node node1 = new Node("423182c4-edb5-11e6-bc64-92361f002671", 1.0, 5.1);
		Node node2 = new Node("42318742-edb5-11e6-bc64-92361f002671", 1.0, 2.4);
		Node node3 = new Node("42318878-edb5-11e6-bc64-92361f002671", 1.0, 3.8);
		Node node4 = new Node("42318968-edb5-11e6-bc64-92361f002671", 1.0, 6.2);
		Node node5 = new Node("42318a3a-edb5-11e6-bc64-92361f002671", 1.0, 4.5);
		
		// Create priority queue
		PriorityQueue<Node> pQueue = new PriorityQueue<Node>();
		
		// Add items to queue
		pQueue.offer(node1); // same code as pQueue.add(node1)
		pQueue.offer(node2);
		pQueue.offer(node3);
		pQueue.offer(node4);
		pQueue.offer(node5);
		
		// Get items from queue
		while(!pQueue.isEmpty()) {
			Node node = pQueue.poll();
			System.out.println(node);
		}
	}

}

데이터를 5개 생성해서, 우선순위 큐 저장소에 저장하고 최종적으로 26번 코드를 통해 5개의 데이터를 우선순위에 따라 꺼내어 화면에 표시합니다. 그 결과는 아래와 같습니다.

42318742-edb5-11e6-bc64-92361f002671(3.4)
42318878-edb5-11e6-bc64-92361f002671(4.8)
42318a3a-edb5-11e6-bc64-92361f002671(5.5)
423182c4-edb5-11e6-bc64-92361f002671(6.1)
42318968-edb5-11e6-bc64-92361f002671(7.2)

[C++] binary_search 정리

메모리 기반의 방대한 데이타가 있다고 가정을 할 때.. 이 데이터 목록 중 어떤 데이타가 존재하는지의 여부를 가장 빠르게 검색해주는 방식이 binary_search인데요. 이 STL 함수를 정리해 봅니다.

먼저 메모리 기반의 방대한 데이터를 준비해 봅니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };

}

데이타가 몇개 되지 않지만 방대하다고 칩시다. binary_search를 사용하기 위해서는 먼저 데이터가 정렬되어 있어야 합니다. 내림차순으로 정렬하는 코드를 추가하면.. 다음과 같습니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };
    auto predicate = [](int a, int b) { return a > b; };
    values.sort(predicate);

    copy(values.begin(), values.end(), ostream_iterator<double>{ std::cout, " "});
    cout << endl;
}

람다 함수로 내림차순 정렬을 위한 기준으로 삼았습니다. 그리고 잘 정렬되었는지 콘솔에 표시해 보았구요. 이제 binary_search 함수를 사용하기 위한 데이터 덩어리가 준비 되었고, 다음처럼 데이터 덩어리중 값 5가 존재하는지의 여부를 검색하는 코드는 다음과 같습니다.

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>

using namespace std;

int main()
{
    list<int> values{ 1, 2, 8, 7 ,6, 5, 4, 1, 9, 3, 5, 6, 7 };
    auto predicate = [](int a, int b) { return a > b; };
    values.sort(predicate);

    copy(values.begin(), values.end(), ostream_iterator<double>{ std::cout, " "});
    cout << endl;

    int wanted{ 5 };
    bool bSearched = binary_search(begin(values), end(values), wanted, predicate);
    if (bSearched) {
        cout << "Found !" << endl;
    } else {
        cout << "Not found." << endl;
    }
}

최단 경로 탐색 – Dijkstra 알고리즘

다익스트라 알고리즘은 시작 노드만을 지정하면, 이 시작 노드에서 다른 모든 노드에 대한 최단 경로들을 분석해 줍니다. 참고로 최단 경로 탐색 알고리즘의 다른 형태로  A*(에이스타) 알고리즘이 있는데요. A* 알고리즘은 시작 노드에서 목적지 노드를 지정해주면 이 2개의 노드 간의 최단 경로 하나만을 분석해 줍니다.

이 글은 다익스트라 알고리즘에 대한 이런 저런 장황한 설명을 배제하고 실제 예를 들어, 그 예에 대한 최단 경로 탐색을 위한 다익스트라 알고리즘에 대해 설명합니다.

먼저 다음과 같은 예를 들어 보겠습니다.

위의 그림을 간단히 설명하면, 0부터 6번까지의 노드(Node)가 존재하고 각 노드를 연결하는 선인 링크(Link)가 있습니다.  각 링크에는 숫자가 표시되어 있는데요. 이 숫자는 해당 링크를 지나갈때에 소요되는 비용(Cost, 경비)로 생각할 수 있습니다. 예를 들어, 2번 노드와 6번 노드 사이에 비용 8인 링크가 있는데요. 이는 2번 노드에서 6번 노드를 지나간다고 할때 8의 비용이 필요하다는 의미입니다. 비용이 적을 수록 상대적으로 더 좋은 경로이겠지요.

이제 위의 예, 즉 0번 노드를 시작점으로 해서 나머지  1, 2, 3, 4, 5, 6 번 노드를 목적지로 하는 최단 경로 6개를 구할 수 있는 방법이 바로 다익스트라 알고리즘입니다.

이상의 문제 해결을 위해 가장 먼저 시작되는 연산은 다음 그림과 같습니다.

위의 그림에서 S 은 이미 처리가 완결된 노드의 집합인데요. 가장 먼저 시작 노드인 0번을 집어 놓음으로써 0번 노드에 대한 처리를 시작합니다. D 저장소는 0번~6번까지의 노드에 대해, 시작노드로부터 소요되는 비용을 저장하고 있습니다. D 저장소의 상단 행은 노드의 번호이고 하단 행은 시작노드로부터 소요되는 비용입니다. 위 그림에서 D 저장소를 보면 0번 노드는 시작노드이므로 소요되는 비용이 0이고, 0번 노드와 링크로 연결된 1번과 3번 노드는 각각 5와 1의 비용이 소요되는 것으로 기록되어 있습니다. T 저장소는 해당 노드로 가는데 연결된 노드의 번호를 담고 있는데요. T 저장소이 상단 행은 노드의 번호이고 하단 노드는 연결된 노드 번호입니다. 위의 그림을 보면 1번 노드와 3번 노드는 0번 노드와 연결되어 있으므로 0으로 기록되어져 있습니다. 이게 주어진 문제에 대한 다익스트라 알고리즘의 첫번째 처리입니다.

이제 다음 처리에 대한 그림을 살펴 보겠습니다.

S집합에 3번 노드를 추가했습니다. 이유는 D 저장소에 3번 노드에 대한 비용값이 가장 최소이기 때문입니다. S집합에 3번 노드를 추가함으로써 3번 노드와 링크로 연결된 2번과 5번 노드에 대한 비용값을 계산해 D 저장소에 기록해야 하는데요. 시작 노드인 0번 노드에서 2번 노드까지 가기 위해서 소요되는 비용은 총 3입니다. 이유는 0번-3번-2번 노드로 가야하므로 비용값은 0번-3번 노드로 가는 비용 1과 3번과 2번 노드로 가는 비용 2를 합한 값입니다. 이러한 계산은 D 저장소를 활용하면 쉽게 계산할 수 있는데요. 이미 시작노드에서 각 노드로 가는 비용이 계산되어 있기 때문에 마지막 노드로 가는 비용만을 더해주면 되기 때문입니다. 5번 노드에 대한 비용은 동일한 방식으로 1+1인 2가 됩니다. 그리고 T 저장소도 각 노드에 대해 이전에 연결된 노드값을 기록해 둡니다. T 저장소의 2번과 5번 노드는 3번 노드를 통해 연결되어 있으므로 3을 기록합니다. 다음 처리에 대한 그림을 살펴봅시다.

S 집합에 5를 추가했습니다. 이는 이미 처리가 완료된 노드 이외의 노드 중 5번 노드의 비용이 2로 가장 최소이기 때문입니다.  이미 처리가 완료된 노드인지 S 집합에 존재하는지를 보면 알 수 있습니다. 5번 노드에 대해 연결된 노드는 2, 3, 6번 노드인데요. 이미 3번은 처리가 완료되었으므로 2번 노드와 3번 노드에 대해 D 저장소의 값을 갱신해야 합니다. 5번 노드를 거쳐 2번 노드를 갈 경우 소요되는 비용은 2+2로 4입니다. 이 값은 이미 전 단계에서 계산된 비용값(3)보다 크므로 무시합니다. 6번 노드에 대한 비용값은 5번 노드까지 오는데 소요된 비용값(2)와 5번에서 6번 노드로 가는데 추가적으로 필요한 비용 3을 합한 값 5이므로, 이 값을 D 저장소에 기록하고 T 저장소의 6번 노드 값에 5번 노드를 기록합니다. 다음 단계로 넘어 갑니다. 

처리가 완결되지 않은 노드 중 비용이 최소인 노드는 2번인데요. 2번 노드와 연결된 노드는 1, 3, 5, 6번 노드입니다. 처리가 완결된 노드를 저장하고 있는 S 집합을 통해 3번 노드와 5번 노드는 더 이상 고려할 필요가 없다는 것을 알 수 있으니, 1, 6번만 고려하면 됩니다. 먼저 1번의 경우 소요되는 비용은 3+1로 4인데요. 이 값은 이전 단계에서 계산된 비용값(5)보다 작으로 D 저장소의 값을 변경 합니다. D 저장소가 변경되면  T 저장소의 값도 2번 노드로 변경합니다. 중요한 부분이므로 위의 그림에서 빨간색으로 표시했습니다. 이제 남은 6번 노드에 대한 비용값을 계산해 보면 3+8로 11인데요. 이 값은 이전 단계에서 계산된 비용인 5보다 크므로 무시합니다. 다음 단계로 진행합니다.

처리가 완료된 노드가 아닌 것 중 1번 노드의 비용이 현재 가장 최소이므로 집합 S에 1번 노드를 추가하고 1번 노드와 연결된 0, 2, 4번 노드 중 완결되지 않은 4번 노드에 대한 비용값을 계산합니다. 4번 노드의 비용값은 4+3으로 7이므로 이 값을 D 저장소에 기록하고 T 저장소에 1번 노드를 기록합니다.

이미 처리가 완료된 노드가 아닌 것 중 최소인 노드는 6번 노드인데요. 이 6번 노드를 집합 S에 추가하고 6번 노드와 링크로 연결된  1, 2, 4, 5 중 처리가 완결되지 않은 노드는 4번인데요. 6번 노드를 경유해 4번 노드로 가기 위한 비용은 5 + 1인 6으로 계산되며, 이 값은 이전 단계에서 계산된 값(7)보다 작으므로 D 저장소가 변경되고, 이와 함께 T 저장소에 대해서도 4번 노드에 대해 6번 노드로 변경합니다.  다음 단계로 진행합니다.

처리되지 않은 노드 중, 이제 유일하게 4번 노드만 남았는데요. 4번 노드와 연결된 1, 6번 노드에 대해 처리를 해야 하는데 이미 1, 6번 노드는 처리가 완결되었으므로 더 이상 진행하지 않고 종료됩니다.

이상의 결과에서 T 저장소를 통해 출발 노드인 0번 노드에서 각 노드에 대한 최단 경로를 파악할 수 있습니다. 0번 노드에서 1번 노드에 대한 최단 경로는 (1번 노드)←(2번 노드)←(3번 노드)←(0번 노드)가 됩니다. 이는 T 저장소를 보면, 먼저 1번 노드에 연결되는 노드는 2번 노드라는 것을 알 수 있고, 다시 2번 노드는 3번 노드와 연결되며 3번 노드는 시작 노드인 0번 노드라고 기록되어 있기 때문입니다. 시작 노드 0번에서 각 노드에 대한 최단 경로를 정리하면 다음과 같습니다.

  • 0번 노드에서 1번 노드 : (1번 노드)←(2번 노드)←(3번 노드)←(0번 노드)
  • 0번 노드에서 2번 노드 : (2번 노드)←(3번 노드)←(0번 노드)
  • 0번 노드에서 3번 노드 : (3번 노드)←(0번 노드)
  • 0번 노드에서 4번 노드 : (4번 노드)←(6번 노드)←(5번 노드)←(3번 노드)←(0번 노드)
  • 0번 노드에서 5번 노드 : (5번 노드)←(3번 노드)←(0번 노드)
  • 0번 노드에서 6번 노드 : (6번 노드)←(5번 노드)←(3번 노드)←(0번 노드)

[C++] Stream을 이용한 Typed Binary 데이터 읽고 쓰기

C++은 stream을 통해 반복자(iterator)를 이용해 데이터를  매우 유연하게 처리 할 수 있습니다. 이 스트림을 통해 데이터의 입력을 키보드나 파일 등을 통해 매우 유연하고 효과적으로 읽고 쓸 수 있는데요. 문제는 일반화된 문자열에 대한 읽고 쓰기에 중심을 두고 있다는 점입니다.  즉,  C++의 스트림을 이용해 실수 타입의 값으로 3.14159265라는 값을 파일에 저장하고자 한다면, 이 값을 메모리에 저장된 그대로의 실수형 바이너리 형식으로의 저장이 아닌 “3.14159265”라는 문자열로 저장되는 것이 C++ 개발자에게 흔히 노출된 방식이라는 것입니다.

다 이유가 있겠으나, 필자는 이 실수형 값을 메모리에 저장된 그대로의 실수형 바이너리 형식으로 파일에 저장하고, 이렇게 저장된 값을 다시 파일을 열어 꺼내와 화면에 표시하고자 합니다.

먼저 3.14159265 값을 d 드라이브의 data.bin에 저장하는 코드입니다.

#include <iostream>
#include <fstream>

int main()
{
	std::ofstream os("d:\\data.bin", std::ios::binary);

	double v = 3.141592625;
	os.write(reinterpret_cast<const char*>(&v), sizeof(double));

	os.close();
}

실행해 보면 data.bin 파일이 생성되어졌고, 파일 크기는 double의 바이트 크기와 동일한 8바이트인 것을 확인할 수 있습니다.

이제 이렇게 생성된 파일에서 다시 실수형 값을 읽어 보는 코드는 다음과 같습니다.

#include <iostream>
#include <fstream>

int main()
{
	std::ifstream is("d:\\data.bin", std::ios::binary);

	double v2;
	is.read(reinterpret_cast<char*>(&v2), sizeof(double));
	
	is.close();

	std::cout << v2;
}

여기서 C++의 template을 이용해 개선을 해보도록 하겠습니다. 즉, 다음과 같은 2개의 템플릿 함수를 추가합니다.

template<typename T>
std::ostream& write_typed_data(std::ostream& stream, const T& value) {
	return stream.write(reinterpret_cast<const char*>(&value), sizeof(T));
}

template<typename T>
std::istream & read_typed_data(std::istream& stream, T& value) {
	return stream.read(reinterpret_cast<char*>(&value), sizeof(T));
}

네, write_typed_data와 read_typed_data는 각각 임이의 데이터 타입에 대해서 지정된 스트림에 메모리에 저장된 그대로의 구조인 바이너리 형식으로 쓰고 읽는 범용 함수입니다.

이 두 함수를 활용해 3.141592625 값을 쓰고 읽는 전체 코드를 다시 작성해 보면 다음과 같습니다.

#include <iostream>
#include <fstream>

template<typename T>
std::ostream& write_typed_data(std::ostream& stream, const T& value) {
	return stream.write(reinterpret_cast<const char*>(&value), sizeof(T));
}

template<typename T>
std::istream & read_typed_data(std::istream& stream, T& value) {
	return stream.read(reinterpret_cast<char*>(&value), sizeof(T));
}

int main()
{
	// Write 
	std::ofstream os("d:\\data.bin", std::ios::binary);

	double v = 3.141592625;
	//os.write(reinterpret_cast<const char*>(&v), sizeof(double));
	write_typed_data(os, v);

	os.close();
	// <- test unit end

	// Read
	std::ifstream is("d:\\data.bin", std::ios::binary);

	double v2;
	read_typed_data(is, v2);
	//is.read(reinterpret_cast<char*>(&v2), sizeof(double));
	
	is.close();

	std::cout << v2;
	// <- test unit end
}

C++에서 스트림을 활용해 타입을 가지는 변수를 바이너리 형식으로 저장하는 위의 방식이 정석인지는 모르겠습니다. (이제와서 이게 무슨 소리? -_-;) reinterpret_cast를 사용했다는 점에서 상당히 의구심이 들기 때문인데요. 더욱 나은 방식이 있다면 제안해 주시기 바랍니다. reinterpret_cast는 포인터가 다른 포인터 형식으로 변환될 수 있도록 하거나 정수 계열 형식이 포인터 형식으로 변환될 수 있도록 하고 그 반대로도 변환될 수 있도록 하는 cast 연산자입니다.