2017년 11월 21일 화요일

Neural Network

http://neuralnetworksanddeeplearning.com/chap1.html

* 0~9까지 숫자 손글씨 인식 예제에 대해 아래와 같이 모델링 가능 함.

x : training input. 28x28=784 크기의 vector 1개.
     vector내 784개 값은 각각 한 픽셀이 grey인지 white인지 구분 (0 or 1)

y : 우리가 기대하는 desired output vector
     숫자는 0~9까지 이므로, vector의 크기는 10.
     가령, 어떤 training input x에 대해 기대하는 인식은 6이라고 하면,
     y(x) = {0,0,0,0,0,0,1,0,0,0}T가 output vector y 임.

* 모든 input x에 대한 신경망의 output이 y(x)에 근사하게 나오도록,
   신경망을 구성하는 각 뉴런의 weight들과 bias 값들을 찾기 위한 알고리즘이 필요 함. [A]

* 현재의 신경망이 얼마나 잘 인식을 하고 있는지 판단하기 위해서 Cost 함수를 도입함.

C(w,b)≡(1/2n) * (sum of || y(x) - a) || ^ 2 for all input x)

w : 신경망 전체의 weight의 집합
b : 신경망 전체의 bias의 집합
a : 입력 x에 대한 현재 신경망의 실제 output vector
C : Cost 함수

모든 training input x들에 대해, 각 x에 대한 각각의 desired out vector와의 차의 벡터 길이의 제곱의 합을 2n으로 나눠 준 것.

* 그러면, 문제 [A]는 이 C의 값을 최소화 시키는 방법을 찾는 것으로 다시 정의 될 수 있음.

* 우선 2개 변수 함수
 C = v1 + v2
 dC = f

dE/dw의 의미

dE/dw
Error derative 이다.
Error derative w.r.t. weight change.
즉 Error의 변화율. weight 변화에 대한 Error의 변화율. 또는 기울기.

Error 관점에서 이해하는게  편함.

[공부] Neural Nework - Backpropagation

Colin McDonnell 선생님의 강좌에 대한 이해를 정리해 본 것.

어쨋든 neural network training에서 이루고자 하는 것은
각 네트워크를 구성하는 weight 값들을 구하는 것이다.

정확하게는 (1) training input과, (2) 그에 대한 output 목표값이 주어질 때
그것으로 neural network을 구성하는 여러 노드들의 weight를 어떻게 보정해 감으로써
"최적"의 weight를 구할 수 있느냐 임.


하나의 뉴런

우선 가장 단순하게 하나의 뉴런부터 시작해 보면,
E : sum of square of all residual errors. 모든 training input과 output 목표값, 즉, 실측치와 목표치의 차이를 제곱한 값들의 합.
(여러 개의 뉴런일 때는, 각각의 output node에 대해 각각 계산.)

통계에서 목표치와 실측치 차이의 수준을 판단할 때 많이 사용하며, 그 차이가 적을 수록 error는 0에 가깝게 되고, 차이가 클 수록 수는 커짐.
sum of squared residuals (SSR) 이라고 부름.

제곱의 합이기 때문에 training case의 수가 많아 질 수록, E 값이 커질 가능성이 높음.


wi : 노드의 입력 weight 중 하나. training의 대상.
z = b + w*x : 각 입력 x와 그 weight의 곱의 합. : 선형 필터.
z의 값에 대해 z값이 특정값 이상이면 1, 이하이면 0을 output하는 것을 생각할 수 있으나,
이 경우 작은 값의 차이에 의해 출력 값이 1과 0으로 너무 크게 변하게 됨.

y = 1/(1+e^-z)
z의 작은 입력 변경에 대해 큰 output 변경 문제를 개선하기 위해, smoothing함수를 사용하는데, 그중 그래프의 변화가 부드럽고, 미분이 쉽고, 미분 결과가 단순하여 1/(1+e^-z)를 많이 사용 함.

Delta rule for learning

http://darkpgmr.tistory.com/133
자신이 한치앞도 잘 안보이는 울창한 밀림에 있을 때 산 정상으로 가기 위한 방법은 간단합니다. 비록 실제 산 정상이 어디에 있는지는 모르지만 현재 위치에서 가장 경사가 가파른 방향으로 산을 오르다 보면 언젠가는 산 정상에 다다르게 될 것입니다.

또는 이와 반대로 깊은 골짜기를 찾고 싶을 때에는 가장 가파른 내리막 방향으로 산을 내려가면 될 것입니다.

그러면, weight 하나가 변할 때, 그에 따른 E의 변화율을 계산해 보면
하나의 training case에 대해서는
dE/dw = dy / dw * dE/dy  = x * dE/dy 가 되며,
dE/dy는 미분하면 y(1-y)가 됨.

때문에
dE/dw = x * y(1-y)가 됨.

그런데 dE/dw를 계산하는 이유는,
이것을 dw를 결정하는데 사용할 수 있기 때문임.
r을 learning rate라 할 때,

dw = r * dE/dw = r * (x * y(1-y))
로 weight를 얼마나 변화시킬지 사용할 수 있게 됨.
즉, 하나의 뉴런의 입력 라인이 5개가 있을 때, 그중 하나의 라인의 weight의 보정은,
dE/dw 즉, w 값이 변화 할 때, E의 변화의 크기 (기울기)와 r을 곱해줘서 구하는 것으로
dE/dw가 크다는 것은 그 만큼 그 w의 변화량이 E의 변화에 크게 영향을 줌으로, 보정을 마찬가지로 그에 비례해주는 것으로, 직관적으로도 무리가 없음.

여러개 training input에 대해서는
dw = Sum (r * (x * y (1-y)))



멀티 레이어 back propagation

Layer가 h (hidden) -> i (hidden) -> j (output) 일 때,
(h와 i는 hidden layer의 노드 의미, j는 output 노드 의미)

목표는 w(h, i) (h노드에서 i노드로 이어지는 weight들의 값)이나,
그 중 우선 i와 j 관계를 살펴 보고 거기서 최종적으로 w(h, i)를 계산하는 것을 보일 수 있다.

i와 j 부터 시작하면,
우선 중요한 것이 변수들 사이의 관계다.
(w(i, j), yi) -> zj -> yj -> E

즉,
E는 yj의 함수이자, zj로도 표현될 수 있으며, wij와 yi로도 표현 될 수 있다.
yi는 zj의 함수이자, wij와 yi의 함수 이다.
zj는 wij, yi의 함수이다.

때문에 E에 대해서는
dE/dyj, dE/dzj, dE/dwij, dE/dyi 등은 가능한 미분이다.
dE/dyj 의미는 yj가 변할 때 E의 변화량 (yj가 E의 변수이므로),
dE/dzj 의미는 zj가 변할 때 E의 변화량
나머지도 마찬가지다.

역시 yj에 대해서는
dyj/dzj, dyj/dwij, dyj/dyi 등이 가능하겠지.

위에 그냥 E라고 표기하는 것은 각 노드 별로 모두 다른 값을 갖게 된다.
편의상 indexing하지 않은 것일 뿐 다 다르다.
즉 output layer의 각 node의 output과 그 기대 값은 당연히 다 다르고, 때문에 E도 다 다르다. hidden layer의 각 node의 E 역시 마찬가지다.

그럼 여러 뉴런이 함께 있는 경우는 hidden node의 dw들은 어떻게 계산 할까 ?
우선 output node는 단일 노드의 경우와 같이 dE/dw를 계산해 낼 수 있다.

그러면 그전 layer인 hidden layer의 한 뉴런의 dE/dw를 어떻게 계산할 것인가가 문제가 된다.

hidden layer의 dE/dw계산은 output layer처럼 단순하지가 않다.

왜냐하면
output 뉴런에 대해서는 그 값이 어떤 값을 가져야 하는 지에 대한 목표값이 training 자료로 주어지지만 중간에 있는 hidden layer들이 어떤 output을 내야 하는지는 사실 아무도 모른다.

때문에 대신에  hidden layer의 dE/dyi를 먼저 계산하고, 그것을 가지고 hidden layer의 dE/dw를 계산해 내는 것이다.

그럼 hidden layer의 dE/dyi를 어떻게 계산 할 것인가가 문제인데,

----------------------------------------------------------------------------------------------------------
생각해보면, 
hidden layer의 yi에 따른 E의 변화율은 output layer에 각 노드에 전파되는데,
역으로 생각하면
그 output layer로 전파된 변화율의 합이 hidden layer의 한 노드의 dE/dyi라 할 수 있다.
----------------------------------------------------------------------------------------------------------

바꿔말하면,
(dE/dyi) of hidden layer  = Sum for all node j in output layer는 (dzj/dyi) * (dE/dzj)
이고, (dzj/dyi)는 wij 이므로,
(dE/dyi) of hidden layer  = Sum for all node j in output layer는 wij * (dE/dzj) 가 된다.

dE/dyi가 구해지면,
dE/dwij = dzj/dwij * dE/dzj 이고, dzi/dwij = yi 이므로
dE/dwij = yi * dE/dzj 이므로
dE/dzj를 이용해서 계산 가능하다.

dE/dzj = dyj/dzj * dE/dyj = y(1-y) * dE / dyj 이므로,
결국
dE/dwij는 dE/dyj로 계산 가능하고,

그 앞 hidden layer를 h layer라 하면 (h -> i -> j)
dE/dwhi는 dE/dyi를 이용해서 계산 가능하다.

이렇게 dw를 training set마다 반복적으로 업데이트 해가는 것이 back propagation이다.

* 다만, dE/dyi를 (ouput node들로의 weight 곱하기 그 노드들의 dE/dzj들의 합)이라는 것을 충분히 이해할 수 있을까 ?

참고
https://www.youtube.com/watch?v=Q0mTl9dQ4_I&t=422s







Output node의 weight는 위와 같은 delta rule을 이용해 계산 가능하다. 문제는 hidden layer의 노드들은 target 값이 없기 때문에, 위와 같은 방법으로는 계산이 불가능 하다. 그럼 어떻게 해야 할까 ?

이때 필요한 것이 Back propagation 알고리즘 이다.

Back propagation 알고리즘.
j는 output layer node 들
i는 hidden layer node 들
hidden activity : hidden layer의 output
zj는 j노드의 logit. 즉, for all i, (wij*yi + b)의 합.
yi는 hidden layer의 한 노드의 output
yj는 output layer의 한 노드의 output




아래에서 잘 이해해야 하는 것이 dE/dyi 계산 시 Summation이 추가 되는  것.



[공부] Neural Network : 기초적인 몇가지 뉴런



1. Linear Neuron : 그냥 입력에 weighted sum이 output 값.
2. Binary threshold Neuron : weighted sum에 대해 bias 이상 인지 아닌지에 따라 1 또는 0을 output 값으로 냄.
3. Sigmoid Neuron : weighted sum에 대해 sigmoid 함수를 적용하여 output을 냄.



1. Linear Neuron


2. Binary threshold Neuron


3. Sigmoid Neuron (Logistic function 사용 )
y = 1 / (1+e^-z)
one over one plus e to -z


2017년 7월 17일 월요일

C++ 주의사항

임시객체가 C++에서는 엄청난 중요도를 갖는다.

임시객체는 타입이름()로 생성한다. 임시객체는 기본은 문장 끝에서 사라진다. rvalue는 그냥 임시객체를 이야기 한다 생각하면 편하다. (물론 숫자 10같은 상수도 rvalue이다.)

함수 리턴이 참조가 아니라 값이면, 그건 언제나 임시객체를 넘기는 것이다.(최적화 되더라도 그렇게 이해 필요 함)

참조는 상수다(별칭). 선언시 반드시 참조할 대상을 지정해야 하며, 다른 대상으로 바꿀 수 없다.

이름이 있느냐에 따라  lvalue와 rvalue가 나뉜다. 가령 임시객체, 리터럴 등은 이름이 없으므로 rvalue다.
(단 const 변수는 변경 불가한 'lvalue' 임)

rvalue는 = 등호의 왼쪽에 올 수 없다.

임시객체는 참조 불가. const 참조 및 리턴값 예외.
복사 생성자는 파라미터로 const Type& a형으로 const 형을 받는데, 그건 임시객체로도 복사 생성이 가능하도록 하기 위한 것이다.

move 생성자는 임시객체처럼 곧 없어질 대상의 내용을 이동시킬 때 사용한다. 그런데 임시객체가 아니더라도 이동시키고자 하는 경우에는 그 객체를 임시객체의 참조 형태로 바꿔준다.

move 생성자는 Type&& a 형태를 받는다.(즉 rvalue reference) 때문에 복사 생성이 아닌 move 생성을 하고자 한다면, 복사할 객체가 rvalue 형태로 넘어와야 한다. 그것을 해주는 것이 move()로, move()는 복사할 객체가 a라면 내부에서는 static_cast<T&&>(a) 해준다.

void doSomthing(Item&& a)에서 &&는 rvalue reference 임.

그런데 탬플릿에서 &&가 쓰이면, 완전하게 다른 의미 임.
template <typename T> 
void doSomething(T&& a)에서 &&는 컴파일시 T에 따라 reference collapse가 일어나는 forwarding reference 임.

lvalue를 탬플릿의 forwarding reference로 넘길 때, 가령 move<T&&>(lv) 에서 타입<>를 생략하면, lvalue는 참조로 타입 추정된다.(즉 T는 lvalue의 실제 타입의 참조로 전달. 그리고 나서 reference collapse 규칙에 따라 & 와 &&이 합쳐져 &이 된다.)

reference collapse는 reference간 충돌이 일어날 때 줄이는 정책이며, &&와 &&이 만날때만 &&이 된다. 충돌이 없을 때는 적용되지 않음.

탬플릿에서 컴파일 시 결정되는 타입들은 꼭 그 바로 앞에 "typename" 키워드를 앞에 붙여주게 되어있음.

auto 우변의 수식으로 타입 추론
decltype() 괄호 안의 수식으로 타입 추론

&없는 auto는 무조건 값이다. 때문에 복사본 생성 및 복사가 일어난다. auto&를 해야 참조다.

auto는 타입을 가져오지만, 참조&나 const 같은 타입의 속성은 가져오지 않는다.

decltype은 원래 변수가 선언된 선언을 그대로 가져와서 교체된다.  다만, () 안에 내용이 단순 변수가 아니라 역참조 등이면, rvalue는 단순형으로, lvalue는 참조형으로 가져온다.

타입의 이름은 typeid(T).name()으로 출력 가능.

부분특수화. 탬플릿의 인자가 특정 값이나 타입 등일 때에 대해 정의할 수 있다.

new는 malloc과 다르다.
malloc은 메모리 할당만 하지만, 
new는 메모리 할당과 생성자 호출 2가지를 한다.

보통 C++에서 메모리만 할당할 때는 operator new를 쓰고
잡힌 메모리에 생성자를 부를 때는 
placement new를 쓴다. new(포인터) T(초기화 파라미터)

shared_ptr의 핵심은 
블럭을 벗어날 때 값 객체는 자동 파괴된다는 점. 값 객체(스마트포인터) 내에 진짜객체의 포인터를 가지고 있다가, 블럭을 벗어날 때 자동 파괴되면서, 진짜객체에 대한 참조개수 낮춤.

weak_ptr의 유일한 존재 이유는 shared_ptr의 상호참조 문제 해결.

객체 초기화 순서
부모생성자 ▷ 내멤버 ▷ 내생성자

서로 연관이 없는 타입에 대해
값 캐스팅은 모든 C++캐스팅으로 불가.
단 reinterpret_로 참조에 대해서는 캐스팅은 가능. 

변환 연산자 즉, operator 타입()을 이용해 좌변을 검사 가능하다.

대입 연산자 통해 우변을 검사 가능하다.

객체의 Exception safety는 
완전 강력 기본 3가지가 있다. 완전은 발생 가능성 없는 경우이고, 강력은 발생 가능하지만 회복하여 객체 재사용 가능. 기본은 회복 보장 안함. STL은 강력을 목표로 설계 됨. Read와 Remove API분리. 
스택 top과 pop 분리.

탬플릿은 type에 대해 실제 사용하는 경우가 없으면 코드화 되지 않는다.

탬플릿의 코드화(치환)은 컴파일 타임에 일어남.

코드화 실패는 에러가 아니다. Substitution Fail Is Not Error

컴파일 타임 타입 검사는 type_traits의 API활용.

enable_if, tag dispatching, constexpr if 등으로 컴파일 시점 탬플릿 치환 정의 가능함.

대표적인 경우가 클래스 복사 연산자에 대해 is_trivial이면 memcpy쓰고, 아니면 하나씩 복사해 주는 것을 탬플릿 인자에 따라 컴파일 시점에 결정 가능.

shared_ptr은 RefManager객체를 별도로 갖는다. RefManager 객체는 
strong : shared 개수
weak : shared + weak 개수
객체에 대한 포인터
destroyer 포인터
가짐. (16바이트 수준)

make_shared는 내부적으로 메모리를 할당하며, 객체와 RefManager의 크기를 한 덩어리로 잡는다. 때문에 객체는 RefManager 파괴조건(mWeak 0)까지 파괴되지 않는다.

함수 선언에 explicit을 써주면 암시적 형변환을 금지 한다. 단, if 문 안에서만 예외적으로 형변환 허용한다. safe bool

if 문 안에 올 수 있는 조건은
bool, 포인터, 실수 3가지이다.

forward declation 된 객체를 그 구현을 모르는 코드에서 delete 할때는 소멸자가 안불린다. 주의 필요.

2017년 7월 11일 화요일

C++ 이디엄

#include <iostream>

/*
1. &연산자도 사용자가 만들 수 있다.
2. &연산자를만들 때는 상수 함수로 만들어야 한다.
*/
using namespace std;

class Point {
int x = 0, y = 0;

public:
const Point* operator&() {
cout << "operator " << endl;
return 0;
}
};

int main() {
Point p;
cout << &p << endl;  // compiler는 보통 연산자를 p.operator&() 형태로 해석 함. 그런데 & 정의가 없음. 왜 동작 함 ?
                    // 컴파일러가 그냥 객체의 메모리를 찍는 것임.

const Point p2;  // const를 붙이면 ? 상수객체는 상수 함수만 부를 수 있음.
                // & 오버로드에 const를 붙이지않으면, &에 대한 디폴트 연산자가 불림.
cout << &p2 << endl;  //  operator

}


#include <iostream>

/*
1. &연산자도 사용자가 만들 수 있다.
2. &연산자를만들 때는 상수 함수로 만들어야 한다.
*/
using namespace std;

class Point {
int x = 0, y = 0;

public:
Point* const operator&() {
cout << "operator " << endl;
return 0;
}

Point* const showAddress() {
cout << "operator " << endl;
return this;
}
};

int main() {
Point pt;
Point *p = &pt; // 그대로 두면 그냥 0이 나온다.
// pt가 포인터로 보이기 때문에 &연산자 오보로드가 불리는 것.
// 1. pt가 포인터가 아닌 타입으로 불리게 해야 한다.

// &(static_cast<char>(pt)); // error발생 함. 관련 없는 타입 캐스팅 실패.

// 2. reinterpret
// char * p1 = &(reinterpret_cast<char>(pt)); // 안됨. 아무리 reinterpret_cast라도 서로다른 타입은 캐스트 안됨. 또한 임시객체 생성 됨.

// 메모리에  pt가 있음. 값으로 캐스팅 하면, 새로 캐릭터 메모리를 새로 만듬. 그리고 기존 메모리의 값을 복사 함. 즉, 임시객체의 주소를 넘겨 받음.

//  서로 다른 타입의 값으로 캐스팅 할 수 없지만, 참조 캐스팅은 가능.
char * p1 = &(reinterpret_cast<char&>(pt));
cout << (void*)p1 << endl;
cout << pt.showAddress() << endl;

// pt2가 상수 객체라면, 또 변경 해야 함.
const Point pt2;
char * p2 = const_cast<char*>(&(reinterpret_cast<const char&>(pt2))); // const_cast : const를 제거 함

cout << (void*)p2 << endl;
cout << pt2.showAddress() << endl;

const Point pt3;
Point * p3 = reinteroret_cast<Point *>(const_cast<char*>(&(reinterpret_cast<const char&>(pt2)))); // const_cast : const를 제거 함

cout << (void*)p3 << endl;
cout << pt3.showAddress() << endl;

}


#include <iostream>

/*
1. &연산자도 사용자가 만들 수 있다.
2. &연산자를만들 때는 상수 함수로 만들어야 한다.
*/
using namespace std;

class Point {
int x = 0, y = 0;

public:
Point* const operator&() {
cout << "operator " << endl;
return 0;
}

Point* const showAddress() {
cout << "operator " << endl;
return this;
}
};


template <typename T> T* xaddressof(const T& obj) {
return reinterpret_cast<T *>(const_cast<char*>(&(reinterpret_cast<const char&>(obj))));
}
int main() {

const Point pt3;
Point * p3 = xaddressof(pt3);

cout << p3 << endl;
// cout << pt3.showAddress() << endl;

cout << addressof(pt3) << endl; // VC++의 addressof는 비상수 객체만 가능.

}

#include <iostream>

using namespace std;

struct Base
{
int value = 10;
void print_address() const { cout << this << endl; }
};

struct Derived : public Base {
int value = 20;
};

int main() {
Derived d;
cout << d.value << endl; // 20

cout << static_cast<Base>(d).value << endl;
// 값 캐스팅은 새로 메모리를 할당하고 거기에 복사가 발생 함 !

cout << &d << endl;
cout << &(static_cast<Base>(d)) << endl; // g++에서는 컴파일 안됨. 임시객체는 주소를 출력 할 수 없다가 c++ 규칙.

// 임시객체의 주소를 출력하려면, 직접은 안되고, 멤버 함수를 통해서 가능하다.
// print_address 멤버를 하나 정의해 줌.
static_cast<Base>(d).print_address();

static_cast<Base&>(d).print_address();
}


#include <iostream>

using namespace std;

class Car {
int mCount=0;
public:
~Car() { cout << "~car" << endl; }

void incStrong() { ++mCount; }
void decStrong() { if (--mCount == 0) delete this; }
};

Car *pObj;

int main() {
Car *p = new Car;
p->incStrong();

pObj = p; // 전역 변수에 저장
pObj->incStrong();

//delete p; // pObj는 없어진 주소를 가지고 있게 됨. 그래서 사용하는 방법이 참조개수 관리하는 방법이 있음.

p->decStrong();
p->decStrong(); // 참조계수가 0이므로 이때 파괴 됨.
}


#include <iostream>

using namespace std;

class RefBase {
int mCount = 0;
public:
virtual ~RefBase() { cout << "~base" << endl; }

void incStrong() { ++mCount; }
void decStrong() { if (--mCount == 0) delete this; }
};

class Truck : public RefBase{
~Truck() { cout << "~tr" << endl; }
};

int main() {

// 기반 클래스의 소멸자가 가상이어야 기반 클래스에서 delete this를 부를 때 가상 함수 테이블을 따라서 가서 자식의 소멸자를 부르게됨.
// 버추얼이 아니면 자식 소멸자 안불림.
// c++진영에서는 버추얼을 싫어 함. 버추얼 테이블 오버헤드가 있음.
}


#include <iostream>

using namespace std;

class RefBase {
int mCount = 0;
public:
~RefBase() { cout << "~base" << endl; }

void incStrong() { ++mCount; }
void decStrong() { if (--mCount == 0) delete this; } // 자식의 객체를 없애기 위해서는 자식 타입으로 캐스트 필요 함.
// 그를 위해서 CRTP를 쓸 수 있음.
};

class Truck : public RefBase {
~Truck() { cout << "~tr" << endl; }
};

int main() {

// 기반 클래스의 소멸자가 가상이어야 기반 클래스에서 delete this를 부를 때 가상 함수 테이블을 따라서 가서 자식의 소멸자를 부르게됨.
// c++진영에서는 버추얼을 싫어 함. 버추얼 테이블 오버헤드가 있음.
}


#include <iostream>

using namespace std;

template <typename T>
class RefBase {
mutable int mCount = 0;
public:
~RefBase() { cout << "~base" << endl; }
void foo() { this } // RefBase * this.
void goo() const { this } // const RefBase * this. // 상수 함수라서 그 안의 변수도 모두 상수로 처리 된다.

void incStrong() const { ++mCount; }
void decStrong() const { if (--mCount == 0) delete static_cast<T*>(this); } // 자식의 객체를 없애기 위해서는 자식 타입으로 캐스트 필요 함.
// 그를 위해서 CRTP를 쓸 수 있음. // this가 상수 함수라서 안됨.
                     //방1 const_cast로 상수성을 없앨 필요 함.   그리고 static_cast
                     //방2 static_cast<const T*>로 한번만 캐스트.
};

class Truck : public RefBase<Truck> {
~Truck() { cout << "~tr" << endl; }
};

int main() {
const Truck *p = new Truck;
p->incStrong();
p->decStrong();
}






#include <iostream>

using namespace std;

/*
트리비얼 : 생성자, 소멸자, 복사 생성자등이 아무일도 하지 않는 경우
트리비얼 생성자 : 생성자 호출없이 malloc만으로 충분하면 트리비얼 생성자라 할 수 있다.
트리비얼 소멸자,
트리비얼 복사 생성자 등.

생성자 trivial의 정의
1. 사용자가 만든 생성자가 없고. B() = default도 trivial 함. B() {}는 non trivial
2. 가상함수가 없고
3. 부모가 없거나 부모의 생성자가 trivial하고
4. 객체형 멤버가 없거나 객체형 멤버의 생성자가 trivial 할 때



*/

class A {};
class B : public A {
int data;
public:
virtual void foo() {

}

};


int main() {
// B의 생성자는 trivial한가 ?
// 아님. 왜냐하면 virtual이 있어서 virtual 테이블이 포함됨.

B* p = (B*)malloc(sizeof(B));
p->foo();
// 크래시 남. 생성자 부르지 않아 발생.
// 가상함수 테이블 초기화를 생성자가 해야 함.

new(p) B; //이미 존재하는 객체메모리에 생성자만 다시 호출 : placement new
}



#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

/*
trivial : big 5 + 소멸자 함수가 하는 일이 있는지 없는지 나타낸다. (생,소, 복, move, move 대입연산자) 메모리를 보는게 아니라 함수가 하는일이 있는지 검사.
standard layout : 메모리 layout이 C와 동일한 구조인가 ?
                  1. 모든 멤버가 동일 접근 지정자
                  2. 가상함수가 없어야 한다.

POD는 : is_trivial(함수의 개념) && is_standard_layout(메모리 배치) 이다.(결국의 C의 구조체와 동일하게 사용가능함. Plain Old Data. memset, memcpy 등을 사용해도 된다는 것.)
                                     C++11에서 정의 된 개념 임.
*/

struct Point {
int x, y;

public:
void foo() {} // virtual 아닌 멤버 함수는 standard나 trivial에 영향 안줌.
Point() = default; // 역시 영향 안 줌.


  //  Point() {}
//virtual void foo() {}
};

int main() {
cout << is_standard_layout<Point>::value << endl;
cout << is_trivial<Point>::value << endl;
}



#include <iostream>
#include <type_traits>

using namespace std;


// 모든 배열을 복사하는 함수
template <typename T> void copy_type(T*dst, T*src, int sz) {
if (is_trivially_copyable<T>::value) {
cout << "copy ini does nothing (trivial)" << endl;
memcpy(dst, src, sizeof(T) *sz);
}
else {
cout << "non trivial" << endl;
while (sz--) {
new(dst) T(*src);
++dst, ++src;
}
}

}

int main() {
char s1[10] = "hello";
char s2[10];

//strcpy(s2, s1);
copy_type(s2, s1, 10);
}



#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

atomic<int> x = 0;

void foo() {

for (int i = 0; i < 1000000; i++) {
//x += 1;
x.fetch_add(1);

/*
__asm {
mov eax, x
add eax, 1
mov x, eax
}
*/
// lock : 하나의 cpu 가 사용하는 메모리를 다른  cpu가 사용하지 못하게 하는 prefix (접두어)
// lock free : OS의 lock 없이 동기화 코드 작성( cpu의 lock 사용.)
/*
__asm {
lock inc x
}
*/
}
}

int main() {

thread t1(&foo);
thread t2(&foo);
thread t3(&foo);

t1.join(); t2.join(); t3.join();

cout << x.load() << endl;
}


#define __ENABLE_ATOMIC_ALIGNMENT_FIX

#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

struct Point {
int x, y;
};

struct Point2 {
int x, y, z;
};

atomic<int> n;
atomic<Point> p;
atomic<Point2> p2;

int main() {
cout << n.is_lock_free() << endl; // CPU lock OK.
cout << p.is_lock_free() << endl; // ?? 크기가 64비트. CPU Lock으로 커버 가능.
cout << p2.is_lock_free() << endl; // 0. 지원 안됨.

Point p4;
p2.store(p4); // 멀티스레드에 안전하게 p=p3된다.

}


#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

struct Point {
int x, y;

void goo(); // aggregate 맞음.
virtual void foo() {  } // aggreage 아님
Point() = default; // aggregate
// Point() { cout << "Point () " << endl; } // C++ class
// Point(int a, int b) :x(a), y(b) { cout << "Point (int, int)" << endl; }

};


int main() {
Point p1;

Point p2(1, 2); // 기존
Point p3{ 1, 2 }; // C++11
Point p4 = { 1, 2 };//C++11 // aggregate : C구조체와 같이 초기화 가능한 것
}


#include <iostream>
using namespace std;

#if 0
class Point {
int x, y;
public:
Point(int a = 0, int b = 0) : x(a), y(b) {}
};

int main() {
Point p1, p2;
Point p3 = p1 + p2; // 방1 : p1.operaotor+(p2);
                   // 방2 : operator+(p1, p2);
// 둘다 만듬 1번 부름. 보통은 2번을 선호 함.
}
#endif


class Point {
int x, y;
public:
Point(int a = 0, int b = 0) : x(a), y(b) {}


Point operator+(const Point&p) {
cout << "mem" << endl;
return Point(x + p.x, y + p.y);
}
/*
friend Point operator+(const Point&p1, const Point &p2) {
cout << "friend" << endl;
return Point(p1.x + p2.x, p1.y + p2.y);
}
*/
};

int main() {
Point p1, p2;
Point p3 = p1 + p2; // 방1 : p1.operator+(p2);
// 방2 : operator+(p1, p2);
// 둘다 만듬 1번 부름. 보통은 2번을 선호 함.

int n = 5;
Point p4 = p1 + n; // Point + int : Point + int 필요. 없더라도 friend 함수 경우 암시적 형변환 된다.
Point p5 = n + p1; // int + Point : int n에 대하여, n.operator+() 정의가 없다. member 함수 구현 경우 에러 발생. 때문에 friend 함수가 이경우 좋다.

// 이항은 friend로. 단항은 member로 만든다.

}


#include <iostream>
using namespace std;

#if 0
class Point {
int x, y;
public:
Point(int a = 0, int b = 0) : x(a), y(b) {}
};

int main() {
Point p1, p2;
Point p3 = p1 + p2; // 방1 : p1.operaotor+(p2);
// 방2 : operator+(p1, p2);
// 둘다 만듬 1번 부름. 보통은 2번을 선호 함.
}
#endif

namespace Util {
class Point {
int x, y;
public:
Point(int a = 0, int b = 0) : x(a), y(b) {}
friend Point operator+(const Point&p1, const Point &p2);
};

Point operator+(const Point&p1, const Point &p2) {
cout << "friend" << endl;
return Point(p1.x + p2.x, p1.y + p2.y);
}

}

int main() {
Util::Point p1, p2;
Util::Point p3;

p3 = p1 + p2; // ?? +는  Util namespace에 있는데 ?? 어떻게 참조 되나 ?.
p3 = Util::operator+(p1, p2); // global에서 +를 찾고, 없으면  인자 p1, p2의 namespace에서 찾는다. 인자기반 검색. ADL. Arguement Depedent Lookup.
// 굳이 operator가 아니더라도 일반 함수도 마찬가지. 인자가 여러가지면 반드시 적어야 함.
p3 = p1 + p2; // 이것은 ADL 때문에 가능함.
}



#include <iostream>
#include <thread>
#include <atomic>


/*
동적 할당 된 메모리 해제
1. void * 타입으로 free()는 가능. 하지만, delete는 절대 안됨. (void * 타입에 대해서는 소멸자를 부를 수가 없음)


*/
using namespace std;

class Test; // 클래스 전방 선언 - 완전한 선언 없어도 포인터는 사용 가능. 단, 실행하면, 소멸자가 안불림 !!!!!!
Test* pt; // imcomplete object(불완전 객체)  delete시 소멸자 호출 안됨 !!!!. 클래스의 전방 선언만 앞에 있는 경우 발생하는 전형적인 문제.
          // 그 방어 책으로, 메모리 해제 하는 함수앞에 sizeof(T)를 해줄 수 있다. sizeof는 당연히 complete 타입만 가능기 때문에 imcomplete  object에 대해서는 에러 발생.
          // checked delete 기법 : boost 진영에서 처음 소개. 요즘에는 static_assert를 이용해서 점검 함.

void foo(Test*p) {
//sizeof(Test); //  기존
static_assert(sizeof(Test) > 1, "error, imcomplete type"); // 최신 방안
delete p;
}

struct Test {
int data;
public:
Test() { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
};

void goo(void *p) { // 절대 안됨 ! 소멸자 안불림.
delete p;
}

int main() {
Test *t = new Test;
foo(t);
}


#include <iostream>
#include <memory>

using namespace std;


int main() {
unique_ptr<int> p1(new int);
unique_ptr<int> p2((int*)malloc(100));
}



#include <iostream>
using namespace std;

class Point {
int x = 0, y = 0;
public:
Point() { cout << "Point () " << endl; }
~Point() { cout << "~Point () " << endl; }
Point(const Point&) { cout << " copy " << endl; }
};

// 책은 RVO를 지원하기 위해서 모든 멤버는 생성자에서 초기화 하자는 이야기 임.
// (다만, 현재는 대부분의 컴파일러가 NRVO를 지원하기 때문에 큰 의미가 없음.)
// 2단 생성자 방식과 충돌 됨.

/*
Point foo() {
Point p;
return p; // "값 타입"으로 리턴하면 임시객체가 리턴됨. return Point(p);의 의미. 성능 저하 있음.
}
*/

Point foo() {
return Point(); // 만들고 리턴하지 말고, 만들면서 리턴하자. RVO return value optimization // 성능 개선 됨.
}

// NRVO : Named RVO --> 이름 있는 객체도 컴파일러가 최적화 하면서 임시 객체가 제거 되는 현상 (2000년대 중반 도입)

int main() {
Point p1;
p1 = foo();
}




#include <iostream>
using namespace std;

int cnt = 0;
class Point {
int x = 0, y = 0;
public:
Point() { cout << "Point () " << endl; }
~Point() { cout << "~Point () " << endl; }
Point(const Point&) { cnt++;  cout << " copy " << endl; }
};

Point foo() {
Point p;
return p;
}

int main() {
Point p1;
p1 = foo();

cout << cnt << endl;
}



#include <iostream>
using namespace std;

int cnt = 0;
class Point {
int x = 0, y = 0;
public:
Point() { cout << "Point () " << endl; }
~Point() { cout << "~Point () " << endl; }
//Point(const Point&) { cnt++;  cout << " copy " << endl; }
Point(const Point&) = delete;// { cnt++;  cout << " copy " << endl; }
};

Point makePoint() {
// Point p;
// return p;
return Point();
}

int main() {
Point p = Point(); // 1. 임시객체 생성
                  // 2. 복사 생성자로 임시객체를 p에 복사
                  // 3. 하지만최적화를 통해서 복사 생성자 호출 제거.
                  // 복사 생성자가 없으면 error
                  // 단 C++17에서는 OK. mandatory RVO : RVO 시 복사 생성자가 없어도 컴파일 됨. g++ -std=c++1z

//p1 = makePoint();
}



#include <iostream>

using namespace std;

// 초기화 순서 !!! 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자
// 소멸자는 생성자가 완벽하게 실행하지 못하면 안불림 !!

// 2단생성자 : 2번의 함수 호출로 개체를 초기화 하는 개념

struct Data {
int * p;

public:
Data() {
p = new int; // 자원 할당

// 그 다음에, DB 접속 시도하다가 실패...
// 생성자에 실패했다는 사실을 외부로 알릴 방법은 exception 또는 flag
// 보통은 exception 사용
throw 1;
}

~Data() { delete p; cout << "~Data" << endl; } // 자원 해제
};

int main() {
try {
Data d;
}
catch (...) {
// 이경우는 소멸자가 안불림. 소멸자는 생성자가 완벽하게 실행하지 못하면 안불림 !!
// 자원 할당 된 리소스 delete할 시점이 없음. memory leak 발생.
// throw 전 release 필요.

}
}


#include <iostream>
using namespace std;
// 초기화 순서 !!! 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자
// 소멸자는 생성자가 완벽하게 실행하지 못하면 안불림 !!
// 2단생성자 : 2번의 함수 호출로 개체를 초기화 하는 개념

// 해결책 2가지가 있음.
// 방안 1: RAII (Resource Acqusition Is Initialize)
// 스마트 포인터 사용
#include <memory>

struct Data {
unique_ptr<int> p;

public:
Data() : p(new int) {
//p = new int; // unique_ptr에서는 생성과 함께 초기화 필요 함. 초기화 리스트로 초기화해야 함.
throw 1;
}

~Data() { cout << "~Data" << endl; } // 자원 해제
};

int main() {
try {
Data d;
}
catch (...) {

}
}



#include <iostream>
using namespace std;
// 초기화 순서 !!! 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자
// 소멸자는 생성자가 완벽하게 실행하지 못하면 안불림 !!
// 2단생성자 : 2번의 함수 호출로 개체를 초기화 하는 개념

// 해결책 2가지가 있음.
// 방안 2: 2단생성자
//

struct Data {
int *p;

public:
Data() : p(0) {} // 이 생성자에서는, 생성자 동작 중 예외 가능성이 있는 어떠한작업도 하지 말자.
void Construct() {
p = new int;
throw 1;
}

~Data() { delete p; cout << "~Data" << endl; } // 자원 해제
};

int main() {
try {
Data d; // 생성자는 예외 가능성이 없기 때문에 정상 동작
d.Construct(); // 이 단계에서 에러가 나더라도, 기 생성자가 에러가 없었기 때문에 ~Data()가 정상 불림.
}
catch (...) {

}
}




#include <iostream>
using namespace std;

void foo(int n) { // 1

}

void foo(void *n) { // 2

}

void goo(char * n) {

}

int main() {
// 0은 정수 임.
foo(0); // 1
foo((void*)0); // 2

    // #define NULL (void*)0 //C의 NULL
    // #define NULL 0        //C++의 NULL

goo(NULL); // void * to char * : C OK. C++ NG.

foo(NULL); // C++ 1로 감.

}



#include <iostream>
using namespace std;

void foo(int n) { // 1

}

void foo(void *n) { // 2

}

void goo(char * n) {

}

// 포인터 0을 만들어 봅시다.
struct xnullptr_t {
  // void*로의 암시적 형변환 제공
//operator void*() { return 0; }

template<typename T>
operator T*() { return 0; }
};
xnullptr_t xnullptr;

int main() {
foo(0);//1
foo(xnullptr); //2

goo(xnullptr); // OK
}



#include <iostream>
using namespace std;
// 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자
struct Buf {
Buf(int sz) { cout << " buf init" << endl; } // 버퍼를 하나 만듬.
};

class Stream {
public:
Stream(Buf* f) { cout << "stream init" << endl; } // 버퍼에 데이터를 씀.
};

// 해결책. buffer를 담은 부모를 만드는 것.

class BufferManager {
protected:
Buf buf;
public:
BufferManager(int sz) : buf(sz) {

}
};
class MyStream : public BufferManager, public Stream {

public:
MyStream(int sz) : BufferManager(sz), Stream(&buf) {

}
};

int main() {
// Buf buf(1024);
// Stream s(&buf);


MyStream ms(1024);
}



#include <iostream>
using namespace std;


// 부모의 멤버와 내 멤버의 초기화 순서
// 1. 코드 순서와 관계 없이 부모의 멤버가 초기화 되고
// 2. 그 다음에 내 멤버의 초기화가 일어 남.
// 3. 그 다음이 자기 자신의 생성자

// 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자

struct Buf {
Buf(int sz) { cout << " buf init" << endl; } // 버퍼를 하나 만듬.
};

class Stream {
public:
Stream(Buf* f) { cout << "stream init" << endl; } // 버퍼에 데이터를 씀.
};


class MyStream : public Stream {
Buf buf;
public:
MyStream(int sz) : buf(sz), Stream(&buf) { // buf 보다 Stream 이 먼저 불린다 !!! 나열 순서랑 상관 없음 !!!!!!

}
};

int main() {
// Buf buf(1024);
// Stream s(&buf);

MyStream ms(1024);
}


#include <iostream>

using namespace std;

// 메모리 복사 없이 구조체(POD) 뒤집기

/*
1. 원래 구조체의 멤버의 순서를 반대로 가진 구조체를 만들자
2.

*/

// pair의 반대 구조체를 정의하고 캐스팅한다는 것이 개념.
template<typename P> struct Reverse {
typedef typename P::first_type second_type;
typedef typename P::second_type first_type;
second_type second;
first_type first;
};


int main() {
pair <int, double> p(1, 3.4);

cout << p.first << endl;
cout << p.second << endl;

cout << p.first << endl; // 3.4
cout << p.second << endl; // 1


}



/*
부모에 friend 선언 되어 있음. operator
상속 받은 클래스의 경우는 부모의 friend 선언 유효 함.

그런데, 자식 클래스에서 friend 함수 operator를 재 선언 가능 함.

그런데, main에서 자식을 부모로 down casting했을 때
그 때는 어떤 << operator가 불리는가 ?

기본은 부모의 <<가 불림.

멤버가 아닌, 일반 함수에 대해서도 virtual을 하고 싶은데, 불가능 함.

일반 함수를 virtual로 만들 방법이 없을 까 ?

->

우선 부모에 virtual 멤버 함수를 만들어 준다. 그리고 friend 함수에서는 그 버추얼 멤버 함수를 불러주도록 하면 된다.
자식도 마찬가지로 virtual 함수만 재정의 해주면 된다. friend 함수는 자식에서는 선언 불필요 함.

즉, 부모 자식 각각 friend 함수 선언하지 않고, friend 함수는 하나 이지만 거기서 부모 자식의 virtual을 불러주게 함으로써
마치 friend 함수가 버추얼인 것처럼 동작 하게 함.

*/



#include <iostream>

using namespace std;

// 초기화 순서 !!!
// 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자

class Base {
public:
Base() { init(); } // 생성자에서는 가상함수가 동작하지 않는다 !!!!
                  // 부모 생성자가 초기화 되는 시점에서는 Derived 의 멤버가 아직 초기화 되어 있지 않다.
virtual void init() { cout << "Base init" << endl; }

};

class Derived : public Base {
int data;
public:
Derived() : data(30) {}
virtual void init() { cout << "Derived init " << data << endl; }
};

int main() {
//Base b;
Derived d;
}


#include <iostream>

using namespace std;

// 초기화 순서 !!!
// 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자

// 해결책. 1. 2단 생성자 ( two phase constructor ! 타이젠은 2단 생성자로 라이브러리 구성)
// 생성자에서는 아무것도 안함. 대신에 Construct()에서 virtual 불러 준다.

class Base {
public:
Base() { } // 생성자에서는 가상함수가 동작하지 않는다 !!!!
      // 부모 생성자가 초기화 되는 시점에서는 Derived 의 멤버가 아직 초기화 되어 있지 않다.
void Construct() { init();  }
virtual void init() { cout << "Base init" << endl; }

};

class Derived : public Base {
int data;
public:
Derived() : data(30) {}
virtual void init() { cout << "Derived init " << data << endl; }
};

int main() {
//Base b;
// Two phase solution. 삼성 타이젠 방식.
Derived d;   // 1단계
d.Construct(); // 2단계
}


#include <iostream>

using namespace std;

// 초기화 순서 !!!!!!!
// 1. 기반 클래스 생성자 2. 멤버 생성자 3. 자신의 생성자

// 해결책. 2.

template<typename T>
class Base {
public:
Base() {  // this->init(); this의 타입: Base*
(static_cast<T*>(this))->init(); // 하지만 위험하다 !!!!  Derived의 data가 아직 초기화 안되어 있다 !!!! 차라리 2단 생성자가 낫다.
}
void init() { cout << "Base init" << endl; }
};

class Derived : public Base<Derived> {
int data;
public:
Derived() : data(30) {}
virtual void init() { cout << "Derived init " << data << endl; }
};

int main() {
Derived d; // 하지만 위험하다.
}



#include <iostream>

using namespace std;



/*
Animal
Dog : Animal
Cat : Animal 일 때

static_cast 무조건 캐스팅 // 컴파일 시간 캐스팅. 성능 저하 없음.
dynamic_cast p가 Dog인지 조사 후 캐스팅 // 실행시간 캐스팅. 성능 저하 있음. 가상 함수가 한개 이상 있어야 함.
Dog가 아니면 0




*/

2017년 5월 17일 수요일

Heavy Page memory





Static page 


Live page


GIF 250MB


[HTTPs page]
[AJAX image loading]

[Images with no div tags]

[Page replaces src of image on the fly]

[Images in the iframes]



[Media]