2016년 9월 20일 화요일

Django test에서 -s 옵션과 Encoding 문제

최근 직종을 변경하여 Django 를 공부하고 있습니다.

간단한 Test code를 돌리다가 우연히 발견한 문제가 있어 정리합니다.

다만, 끝까지 확인한 결론은 아니고 일종의 추측이 섞여 있으니 주의하세요.


문제: "manage.py test -s XXX" 와 같이 -s option을 주면 문제가 없는 Test case가 -s option을 빼면 에러가 발생.

우선 -s option이 하는 일은 이렇습니다.

-s, --nocapture       Don't capture stdout (any stdout output will be
                        printed immediately) [NOSE_NOCAPTURE]

stdout을 캡쳐하지 않는 옵션인데 이게 왜 영향을 주는 것일까요?

Test case의 문제 부분은 이렇습니다.

a = u'한글'
print(a.encode('utf8'))

그냥 유니코드를 프린트 하기 위한 문장으로 특별하지 않죠? (일반적으로 encode를 빼도 print가 알아서 잘해주지만 문제 발생을 위해 그냥 둡니다)

그런데 -s를 빼면 아래와 같은 에러가 발생합니다(일부 내용은 지웠습니다)

  File "***/lib/python2.7/site-packages/nose/plugins/capture.py", line 125, in _get_buffer
    return self._buf.getvalue()
  File "***Python.framework/Versions/2.7/lib/python2.7/StringIO.py", line 271, in getvalue
    self.buf += ''.join(self.buflist)

UnicodeDecodeError: 'ascii' codec can't decode byte 0xea in position 0: ordinal not in range(128)

StringIO가 문제군요-

알아보니 StringIO는 일종의 버퍼로 많이 쓰는데요, 유니코드 처리에도 문제가 없습니다.(cStringIO는 유니코드에 문제가 있습니다)

다만, 유니코드와 ascii가 섞일 경우가 문제가 생기는데요, 제가 생각하는 문제의 원인이 이 부분 입니다.

-s를 빼면 caputure를 위해 capture.py의 다음의 코드가 동작하는 것 같습니다.

    def start(self):
        self.stdout.append(sys.stdout)
self._buf = StringIO()
        sys.stdout = self._buf

문제는 기본 sys.stdout의 encoding이 ascii인 경우이고 제 경우 실제 기본 값이 ascii로 되어 있었습니다.

때문에 StringIO로 열어둔 스트림의 중간에 unicode와(제가 원하는 테스트코드의 인코딩) ascii(stdout의 기본 인코딩)이 섞이면서 상기와 같은 에러가 발생한다고 추측(!) 했습니다.

pdb까지 찍어서 확인하고 싶었지만, 왜인지 pdb를 붙이면 에러가 나고 또 원래 하려던 일도 아니기에 이정도 추정을 하고 본 글을 정리합니다.

2016년 7월 4일 월요일

구간 최소 트리(Range minimum query)

알고스팟의 MORDOR 풀기 위해 '구간 최소 트리'(이하 RMQ)를 구현했습니다.

RMQ에 관해서 간략하게 설명하자면,

'특정 구간에서 최소값은 무엇인가?' 에 대한 답을 구하기 위해 binary tree를 응용해서 최소값을 구할 수 있는 tree를 미리 구현하고 이를 활용하는 것입니다.

예를 들면 0~9까지 10개의 길이를 가지는 어떤 배열에서 다음의 질의(Query)에 답할 수 있습니다.

0~5 중 가장 작은 값은?
2~9 중 가장 작은 값은?

subarray의 min으로도 처리 할 수 있는데, subarray 생성시간 + min 연산시간(O(n)) 이 소모되는 반면, 이미 구성해둔 tree에서의 search 는 O(logn) 의 시간에 답을 구할 수 있습니다.

기본 원리는

1. 각 구간을 binary tree로 쪼갬
2. 각 구간 중 가장 작은 값을 저장

위의 예에서 다음과 같은 값들이 저장됩니다.

0~9: 가장 작은 값
0~4: 가장 작은 값, 5~9: 가장 작은 값
0~2: 가장 작은 값, 3~4: 가장 작은 값, 5~7: 가장 작은 값, 8~9: 가장 작은 값
각각의 leaf에는 자기 자신 저장

이와 같은 형태로 구성됩니다.

Query로 구간(Range)를 받으면, 생성된 Tree에서 어떤 Node가 필요한지를 알아낸 뒤에 그 중 가장 작은 값을 Return 합니다.
예에서 0~5라면 0~4의 최소 값를 저장한 노드와 5 (는 하나니까)노드가 필요합니다.
이 중 작은 값을 Return 하면 됩니다.

이런 알고리즘의 구현이 '알고리즘 문제해결 전략' 책에는 C로 되어 있는데, 그냥 Python 으로 옮겼습니다.

여기에는 Binary Tree 구현을 위해 Array를 사용했는데요, Left와 Right를 odd, even으로 mapping 시켰습니다. 비교적 꽉 찬 이진트리의 경우 이렇게 구현하는 것이 메모리를 좀 더 효율적으로 쓴다고 하니 참고하세요.

Array의 길이는 주어진 n과 가장 가까운 2의 제곱 수에 2를 곱한 만큼입니다.

가령 n = 6이면 가장 가까운 2의 제곱 수인 8(2^3) 에 2를 곱한 16입니다.

구현에서는 이것이 귀찮으므로 넉넉하게 계산했습니다.

#!/usr/bin/python
import sys

class RMQ(): #Range Minimum Query
    RMList = []
    length = 0

    def __init__(self, targetList):
        self.length = len(targetList)
        self.RMList = [""]*self.length*4
        self.init(targetList, 0, self.length-1, 1)

    def init(self, targetList, left, right, nodeIdx):
        if left == right:
            self.RMList[nodeIdx] = targetList[left]
            return targetList[left]

        mid = (left + right) / 2
        leftMin = self.init(targetList, left, mid, nodeIdx*2) # left idx is even number
        rightMin = self.init(targetList, mid+1, right, nodeIdx*2+1) # right idx is odd number
        self.RMList[nodeIdx] = min(leftMin, rightMin)
        return self.RMList[nodeIdx]

    def _query(self, left, right, nodeIdx, nodeLeft, nodeRight):
        if right < nodeLeft or nodeRight < left: return sys.maxint
        if left <= nodeLeft and nodeRight <= right: return self.RMList[nodeIdx]

        mid = (nodeLeft + nodeRight) / 2
        return min(self._query(left, right, nodeIdx*2, nodeLeft, mid),
                   self._query(left, right, nodeIdx*2 + 1, mid+1, nodeRight))

    def query(self, left, right):
        return self._query(left, right, 1, 0, self.length - 1)

책의 구현이 워낙 깔끔해서 더 수정할 부분은 없는 것 같습니다.

다만 이렇게 구현하고, 문제를 도전(!!!) 했으나 시간초과.......

보니까 C,C++,Java 만 문제를 풀었더군요-

pypy로도 안되는 것을 보면, 아무래도 Python 으로는 한계가 있는게 아닐까 싶습니다.

좀 더 빠르게 하는 방법이 있으면 생각해봐야죠-

2016년 5월 12일 목요일

N까지의 소수 검사할때 N의 제곱근(square root of N) 까지만 하는 이유

N까지의 소수 검사 관련 코드를 조금 더 빠르게 하는 여러가지 방법이 있습니다.

그중 이해하기 쉬운 테크닉(?) 중 하나가 N까지 검사하지 않고 N의 제곱근( square root of N) 까지만 검사하는 것인데요, 코드로 설명하면 다음과 같습니다.

def isPrime(n):
    if n == 1: return False
    if n == 2 or n == 3: return True
    if n%2 == 0: return False
    for i in xrange(3, int(n**0.5)+1, 2):
        if n%i == 0:
            return False
    return True

이것이 성립하는 이유가 좀 궁금해져서 알아보고 생각해봤습니다.

어떤 수 n이 1과 자신이 아닌 두 수의 곱으로 되어 있다고 생각해봅시다. (즉, 소수가 아님)

n = a*b 이고 n' 은 n 의 제곱근이라고 표현합시다.

여기서 만약,

a >= n' 이면, a*b = n = n'*n' 이므로 b<=n' 가 됩니다.

따라서 n' 까지만 검사를 하면 합성수를 이루는 a, b 중 작은 수(위에서는 b)까지는 충분히 체크할 수 있고, 합성수가 존재하지 않으면 소수라고 할 수 있습니다.

List에서 두 원소와 두 원소 사이의 길이를 합을 cost로 할때 최대값 구하기

간단한 문제 풀일이 있었습니다.

List가 있을 때,

value1 + value2 + abs(index(value1) - index(value2))

의 최대값을 구하는 문제!

그냥 전부 다 해보는 취지로 다음과 같이 처음에 접근했습니다.

    def oriSolution(self, A):
        r = -1000000001
        l = len(A)
        if l == 1: # length is 1
            return 0

        for P in range(l):
            for Q in range(P+1, l):
                t = A[P] + A[Q] + (Q-P)
                r = max(r, t)
        return r

특별할 부분은 없는데, O(n^2) (등차수열의 합이니 아마)의 연산비용이 나오는게 영 꺼림직 해서 고민을 좀 해봤습니다.

현재의 최대값인 r(그러고보니 변수 이름이 적절하지 않네요)을 갱신하는 알고리즘인데,

안쪽의 Loop를 생략 할 수 있는 case가 생길 것 같았습니다.

우선 cost는 위에 기술된 것처럼 A[P] + A[Q] + Q-P (항상 양수) 로 기술됩니다.

현재의 최대값이 만약 A[P] + A[Q] + Q-P 에 의해 갱신이 되는 경우에만 안쪽 loop가 유효 하겠죠.

때문에 이걸 생략 할 수 있는 case를 만들었습니다.

현재의 최대값을 curMax 라고 하면,

curMax <= A[P] + A[Q] + (Q-P) 이면 의미가 있습니다.

이는,

curMax - A[P] - (Q-P) <= A[Q] 

으로 표현이 가능한데요, 만약 Q를 확인하지 않아도 이를 만족하지 않음이 자명하다면 안쪽 loop는 생략해도 괜찮습니다.

이를 위해 최대값을 미리 구해둡니다. (max(A)인데 O(n)의 비용으로 구해집니다. 1번만 하면 되므로 그리 큰 비용은 아닙니다)

curMax - A[P] -(Q-P) 가 만약 리스트 전체에서 가장 큰 값보다 크다고 하면, 

즉, curMax - A[P] - (Q-P) > maxValue 이면

curMax - A[P] -(Q-P) <= A[Q]

는 언제나 틀립니다.

따라서 curMax - A[P] - (Q-P) > maxValue 인 경우는 안쪽 Q loop는 돌지 않아도 됩니다.

여기서 Q-P 계산에 Q 가 사용되어서 문제인데,

Q-P의 최대값은 List의 길이를 넘지 않으므로 그냥 l로 치환합니다. 이렇게 해도 논리적 흐름에는 영향이 없으므로 괜찮습니다.

코드는 이렇습니다.

    def betterSolution(self, A):
        r = -1000000001
        l = len(A)
        if l == 1: # length is 1
            return 0

        maxV = max(A)

        for P in xrange(l):
            if r - A[P] - l > maxV:
                continue
            for Q in xrange(P+1, l):
                t = A[P] + A[Q] + (Q-P)
                r = max(r, t)
        return r

별거 아닌 변화 같지만, random한 case에서 속도 향상이 유의미 하네요.

        A = []
        for _ in range(5000):
            A.append(random.randint(1,1000))

        startTime = time.time()
        self.assertTrue(P().betterSolution(A))
        print("{0}: {1}".format("Better", time.time() - startTime))

        startTime = time.time()
        self.assertTrue(P().oriSolution(A))
        print("{0}: {1}".format("Ori", time.time() - startTime))

        self.assertEquals(P().oriSolution(A), P().betterSolution(A))

와 같은 test code에서

Better: 0.205455064774
Ori: 1.86105298996

의 결과가 나오네요.

혹시 잘못생각한 부분이 있다면 comment 주세요 :)

2016년 5월 10일 화요일

jws rbtree 분석(작성중..)

Python 의 RBTree에 대해 파악하다 http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx 를 알게되었습니다.

유용한 내용이 많아 찬찬히 업데이트 합니다.

1. Left, Right 대신 0, 1

left, right 표현 대신 0, 1을 쓰는 저자의 코드도 흥미롭네요.

/* Classic binary search tree insertion */
struct cbt_node *cbt_insert(struct cbt_node *root, int data)
{
    if (root == NULL)
    {
        root = make_node(data);
    }
    else if (data < root->data)
    {
        root->left = cbt_insert(root->left, data);
    }
    else
    {
        root->right = cbt_insert(root->right, data);
    }

    return root;
}

/* Julienne Walker's binary search tree insertion */
struct jsw_node *jsw_insert(struct jsw_node *root, int data)
{
    if (root == NULL)
    {
        root = make_node(data);
    }
    else
    {
        int dir = root->data < data;

        root->link[dir] = jsw_insert(root->link[dir], data);
    }

    return root;
}
가독성을 조금 희생시킨 것 같지만 라인수를 많이 줄여줍니다. 내용을 이해한다면 사실 가독성이 많이 떨어진다고 하기도 애매하네요 ㅎㅎ

python에서도 무리 없이 써먹을 수 있을 것 같습니다.

가령

>>> (1>0) == (True)
True
>>> (1<0) == (False)
True
>>> (1>0) == (1)
True
>>> (1>0) == (0)
False
>>> (1<0) == (0)
True
>>> (1<0) == (1)
False

좋네요

Python 의 RBTree

손고리즘 스터디에서 재경님이 알려주시길 C++에서는 Map이 RBTree로 되어 있으나 python 은 RBTree 구현체가 없다고 합니다.

C/C++이랑 Python이 결속력(?)이 좋아서 특별히 안만든게 아닐까 하는 생각도 있지만, 뭐 일단 좀 의문이네요-

그래서인지 Tree 구현체가 많이 있는데, pip를 통해 쉽게 설치 할 수 있는 bintrees가 유용한 것 같습니다.

어떻게 RBTree를 구현했는지 코드를 좀 파악해야겠네요 :)

https://github.com/doctorpangloss/bintrees

2016년 5월 2일 월요일

Python django

예전에 개인적인 호기심으로 잠깐 만저본 django.

이번에도 호기심 차원에서 조금 더 만져보고 있다. 전에 기억이 하나도 안나서 아예 처음부터 하는 기분인데, 참 잘 만들어진 것 같다.

생산성이 수십배 오르는게 당연하겠구나.

2016년 3월 29일 화요일

Python lambda function 과 unittest

수학식이 좀 많이 나오는 것 같은 문제를 풀다보니 아래와 같은 함수를 만들 일이 생겼습니다.

    def getFunction(self, c1, c2):
        x1 = c1[0]; y1 = c1[1]
        x2 = c2[0]; y2 = c2[1]
        if x1 == x2:
            return (lambda x,y: x1)
        if y1 == y2:
            return (lambda x,y: y1)
        return (lambda x,y: (y1-y2)/(x1-x2)*x + y1 - (y1-y2) / (x1-x2)*x1)

두 점을 지나는 직선을 구하는 함수로 함수 자체를 리턴시키기 위해 lambda를 사용했습니다.

보통 TDD를 하기에 TestCase를 만들긴 했는데, 생각보다 고민이 되더군요-

"기대값을 뭘해야 하지?"

종이에 쓰는경우면 쉽습니다.

가령 (0,0), (1,1)을 지나는 함수 f(x)는 이렇게 되겠죠.

f(x) = x

unittest에 넣는다고 치면(당연히 안되지만)

assertEquals(f(x) = x, getFunction((0,0), (1,1)))

과 같은 포멧이 될텐데 기대값에 f(x) = x 와 같은 형태는 둘 수 없습니다.

String 으로 뽑아도 당연히 저렇게 나타나지 않구요-

일단 대안으로는 기대함수(편의상)에 값을 넣었을 때 올바르게 나오는지 검사하도록 했습니다.

다음과 같네요.

        # y = 2*x function
        F = Treasure().getFunction((0,0), (1,2))
        self.assertEquals(0, F(0,2))
        self.assertEquals(2, F(1,2))
        self.assertEquals(10, F(5,10))

대략 적으로 검출은 되는데, 영 찝찝합니다.

뭔가 lambda function 을 unittest에서 적절하게 검출하는 방법이 있으면 좋을 것 같습니다.

검색을 좀 해보니 eval을 만들어 string으로 비교하는 방법도 있는 것 같은데, 이정도가 최선일까요? 고민해봐야겠네요 ㅎ

2016년 3월 17일 목요일

정리

정리를 그렇게 잘하지는 못하는 것 같습니다.

블로그도 그렇고 메모도 그렇고.

그래도 일단 쓰는게 안쓰는 것보단 좋겠져.

2016년 2월 22일 월요일

STRJOIN 문제에서 python list의 sort 개선!

비교적 쉬운 카테고리인 'Greedy algorithm' 에 속하는 STRJOIN 문제를 풀었습니다.

https://algospot.com/judge/problem/read/STRJOIN

간만에 술술(...) 풀렸고, 혹시해서 가장 짧은 정답을 확인하니 3줄에 짜신 분도 있더군요-_-..


여튼, 제가 푼 풀이에서 중요한 부분은 sorting 이었습니다.

주어진 list를 갱신하고, 이때마다 '최소값'을 알기 위해서 list를 sort하였는데요- 문제 요구사항에 주어진 list의 길이가 그리 길지 않아 이 부분을 더 생각하진 않고 그냥 계속 sort 시켰습니다.

이런식으로 할 수 있겠죠.

a = [..data..]

sorted(a) # 내장함수를 사용
혹은
a.sort()

TDD로 접근하다보니 사실 별로 신경 쓰지도 않는 시간에도 괜히 한번 눈길이 가는데요,
재미있는 점은 sorted() 와 같은 함수는 성능이 그다지 좋지 않더군요. (아무래도 일반적으로 쓸 수 있어서 그런 것 같네요)

당연하게도 리스트에 특화된 a.sort() 의 경우가 훨씬 빠른 성능을 보였습니다.

이쯤에서 왠지 sort() 를 이기고 싶은 생각이 들었습니다.

뭐 언제나 모든 정렬에서 sort() 를 이기려는게 아니고 이 문제에 특화된 경우에라도 이기고 싶었죠.

(스포주의)
문제의 힌트가 될수도(...뭐 도움 안될수도) 있지만 풀이를 설명하자면 이렇습니다.

1. 정렬된 리스트를 받습니다.
2. 종료 조건으로 리스트의 길이를 검사합니다.
3. 가장 앞의 두 값이 가장 작은 두 값이므로, 이 두 값을 합해서 cost를 만듭니다.
4. 만들어진 cost를 누적합니다.
5. 새로운 값을 리스트에 다시 넣고 1을 반복합니다.

코드로 쓰면 대략 이렇습니다. (본 코드에서는 새로운 값을 가장 뒤에 붙였습니다. append)

    def calCost(self, sortedList):
        totalCost = 0
        while len(sortedList) >= 2:
            sortedList.sort()
            firstMin = sortedList[0]
            secondMin = sortedList[1]
            cost = firstMin + secondMin
            totalCost = totalCost + cost
            sortedList = sortedList[2:]
            sortedList.append(cost)
        return totalCost

이미 리스트 연산에서 많은 낭비가 생기지만(...) 무시하고, sort()의 개선이 성능에 가장 큰 영향을 미친다고 생각했습니다.

이를 다음의 TestCode로 돌려보면,

    def testSolveWithDefaultSort(self):
        self.assertEquals(3222, StrJoin().calCost(
            [1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,
             1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,
             1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,
             1,1,1,1,1,1,1,1,2]))

testSolveWithDefaultSort (__main__.StrJoinTest) ...
__main__.StrJoinTest.testSolveWithDefaultSort: 2.6798248291

시간은 너무 작아서 제가 임의로 *10000 을 한 값을 출력했습니다. (그러니까 2초씩이나 걸리지 않습니다...)

def tearDown(self):
        print("{0}: {1}".format(self.id(),
                                (time.time() - self.startTime)*10000)) # * 10000

우선 뼈대를 위한 코드는 이렇습니다. 위와 동일한데 단지 sort를 sortMethod를 통해 연산합니다. sortMethod는 제가 만들 함수입니다. (정확하게는 sortMethod는 아니지만 비교를 위해 이런 이름을 붙였습니다.)

    def calCostWithSortMethod(self, sortedList, sortMethod): 
        totalCost = 0
        sortedList.sort()
        while len(sortedList) >= 2:
            sortedList = sortMethod(sortedList) # Custom
            firstMin = sortedList[0]
            secondMin = sortedList[1]
            cost = firstMin + secondMin
            totalCost = totalCost + cost
            sortedList = sortedList[2:]
            sortedList.append(cost)
        return totalCost

이를 다음과 같이 TestCode로 검증합니다.

self.assertEquals(26, StrJoin().calCostWithSortMethod([3,1,3,4,1], self.mySort))

TestCode 에서 적절한 sort를 작성합니다.


그럼 원 문제인 sort() 를 한번 다른 방법으로 개선 시켜보겠습니다. (TestCode의 mySort 작성)

본 문제의 특징은 이렇습니다.

1. 이미 정렬된 값이 들어옴
2. 새로운 값이 어디에 들어갈지만 알면됨

즉, 매번 모든 리스트 데이터가 '정렬되지 않음'을 가정 할 필요가 없습니다.

1개의 데이터만 올바른 위치에 insert하면 끝이기에 아 이러면 sort() 를 이길 것 같아서 시도했습니다.

1번째 시도는 그냥 앞에서 부터 비교하기 입니다.

왜냐하면 순서대로 정렬된 값에서 가장 작은 두 값을 합한 결과가 앞쪽에 위치할 확률이 더 높다고 생각했기 때문이죠.

1번 시도의 코드는 이렇습니다. (pop() 으로 위에서 뒤에 붙이 값을 꺼냅니다)

    def mySort(self, l):
        newValue = l.pop()
        for idx in xrange(len(l)):
            if l[idx] > newValue:
                l.insert(idx, newValue)
                return l
        l.append(newValue)
        return l

결과는?!

testSolveWithCustomMethod (__main__.StrJoinTest) ...
__main__.StrJoinTest.testSolveWithCustomMethod: 5.04970550537

졌습니다. ㅠㅠ 

아무래도 앞에서 부터 비교하는 것이 그리 좋은 접근이 아닌 것 같네요.

2번째는 '일반적인' 접근인 binary search 를 사용했습니다.

즉, 반으로 나눠서 위치를 잡아가는 방법이죠.

다음과 같습니다.

    def myImproveSort(self, l):
        newValue = l.pop()
        start = 0
        end = len(l) - 1

        while True:
            if start == end:
                if l[start] > newValue:
                    l.insert(start, newValue)
                    break
                else:
                    l.insert(start+1, newValue)
                    break
            if start > end:
                l.insert(start, newValue)

            mid = (start+end)/2
            if l[mid] < newValue:
                start = mid + 1
            elif l[mid] > newValue:
                end = mid + 1
            else:
                l.insert(mid, newValue)
                break

        return l

결과는!!

testSolveWithCustomImproveMethod (__main__.StrJoinTest) ...
__main__.StrJoinTest.testSolveWithCustomImproveMethod: 3.24964523315

원래 결과는

testSolveWithDefaultSort (__main__.StrJoinTest) ...
__main__.StrJoinTest.testSolveWithDefaultSort: 2.6798248291

방법1 보단 훨씬 나아졌지만 역시 졌네요..

List가 더욱 커지면 효과가 나지 않을까 싶지만, 일단 거기까진 안했습니다.

역시 List에 내장된 sort() 가 짱입니다... 다만, 제가 구현한 Code에서 개선될 수 있는 부분이 좀 있을 것 같으니 얼추 비등해지지 않을까요? (희망사항이겠죠)

결론: 특수한 경우지만 이기지 못하였다. 하지만 더 크기가 커지거나 몇몇 부분을 개선하면 이기지 않을까?.. (Code에 cost가 큰 연산이 좀 있는 것 같기도...)






2016년 2월 16일 화요일

POD(Plain Old Data)

Google C++ Style Guide ( https://google.github.io/styleguide/cppguide.html ) 를 확인하다보니 POD에 관한 내용이 나옵니다.

Static and Global Variables

Variables of class type with static storage duration are forbidden: they cause hard-to-find bugs due to indeterminate order of construction and destruction. However, such variables are allowed if they are constexpr: they have no dynamic initialization or destruction.
Objects with static storage duration, including global variables, static variables, static class member variables, and function static variables, must be Plain Old Data (POD): only ints, chars, floats, or pointers, or arrays/structs of POD.

POD(Plain Old Data)가 뭔지 몰라서 검색하고 정리합니다.

http://1stpasa.tistory.com/13 과 http://www.gpgstudy.com/forum/viewtopic.php?t=10067&sid=4598bc316bf03f5c1f90aecdad41e177 의 내용을 기반합니다.

POD란 C의 struct나 build-in-type과 동일한 메모리 구조를 갖는 Object를 의미합니다.

따라서 build-in-type과 같이 쓸 수 있는 타입이죠.

memset, memcpy에 의한 단순 메모리 복사가 가능한 구조이며 다음의 조건을 가진다고 합니다.

1. built-in type
2. 가상 함수가 없고 사용자 정의 할당자(allocator)와 소멸자를 가지지 않는 Class의 Object
3. non POD를 non-static 멤버로 가지지 않는 Class의 Object

경우에 따른 메모리 구조 변경이 일어나지 않으면 POD로 생각하면 될 것 같네요.

1은 이해가 되고, 3의 에서 static에 관한 내용은 static은 객체에 포함되는 멤버가 아니므로 non-POD가 static으로 있는 것은 괜찮지만 그렇지 않으면 당연히 POD가 안되겠지요. 좀 더 쉽게 쓰면 "POD만을 non-static으로 가져야 POD다." 가 됩니다. (말이 어려웠네요)

2번째 조건에서 사용자 정의 할당자(allocator)에 대해서는 조금 설명이 필요합니다.

사용자 정의 할당자는 정의가 되어 있건 아니건, 할당 위치에 영향을 미치고 객체 내부의 메모리 배치에는 영향을 주지 않습니다.

그럼 POD여도 될 것 같은데 그렇지가 않습니다.

위 사이트에 나온 Code로 설명하겠습니다.

다음의 코드의 할당문( = )과 memcpy의 결과는 동일합니다.

class POD
    {
    int x_;
    char* buf_;
    }
 
POD t1;
POD t2;
...
t1 = t2;
memcpy(&t1, &t2);

그러나 다음과 같이 사용자 정의 할당문( 연산자 "=" 오버로딩)을 만들면 memcpy와 결과가 다릅니다.


class NPOD
    {
    int x_;
    char* buf_;
 
    NPOD() : buf_(0) {}
    ~NPOD() { delete buf_; }
    NPOD& operator=(const NPOD& other)
        {
        x_ = other.x_;
        int bufLen = strlen(other.buf_);
        buf_ = new char[bufLen+1];
        memcpy(buf_, other.buf_, bufLen);
        }
    }
 
NPOD t1, t2;
...
t1 = t2;
memcpy(&t1, &t2);


주의 깊게 작성하면 동일 할 수도 있겠지만(아마도) 위와 같이 그렇지 않을 수도 있습니다.따라서 C++에서는 non-POD로 규정하여 C style 메모리 관리 함수 사용을 금지합니다.