반응형
728x90
반응형

C++은 다른 언어들처럼 가비지 컬렉터가 없다ㅋㅋ 그래서 메모리 관리를 제대로 하지 않으면, 정확히 Heap 영역에 대한 메모리 관리가 제대로 되지 않는다면,

헬이다. 헬.

그러면 어찌해야하는가? RAII 라는 디자인 패턴이 있다.

Resource Acquisition Is Initialization 

위를 줄여서 RAII 라고 부른다.

RAII의 유래

그럼 왜 RAII일까? 유래를 좀 찾아봤는데 자원의 획득은 초기화이고, 반대로 자원의 반환은 객체 소멸 뭐 이런 식으로 확장하여 나타내는 어떤 관용구 같은 느낌의 디자인 패턴 개념이다.

RAII란?

좀 더 자세하게 관련해서 정의를 알아 보자면 RAII 는 객체의 생명주기에 관련된 내용으로, C++에서 객체가 생성되고 사용된 Scope를 벗어나면 객체의 자원을 해제해주는 방법을 이야기한다.

즉, 힙에 동적 할당되는 객체라고 해도, 프로그램 실행 흐름이 해당 객체의 유효 scope를 벗어나면 객체를 메모리 해체하도록 하는 것이 RAII라고 생각하면 될 것 같다.

근데 아쉽게도 힙 영역에 할당된 객체는 지역 scope를 벗어난다고해서 동적할당된 부분이 해체되지 않는다. 왜냐하면 동적할당이 이루어지는 해당 scope 내에는 객체 포인터가 스택에 쌓이고, 해당 scope가 끝나면 그 객체 포인터만 스택에서 소멸된다. 동적할당된 메모리는 힙 영역에 그대로 남아있다.

다음과 같이 말이다.

번호순으로 읽기

위의 사실을 응용하여,

어떤 scope에서 동적 할당이 이루어졌다면, 해당 scope에서 스택 영역에 할당되는 요소에게 동적 할당한 주소를 넘겨주고, 그 스택 영역에 할당된 요소는 스택에서 제거될 때, 넘겨받은 동적 할당된 주소를 함께 delete 하면서 제거되면 되지 않을까? 라는 발상이 떠오른다. 아래와 같이 말이다.

번호순으로 읽기

문제가 되는 부분

자, 일단은 문제가 되는 코드이다. 위의 그림과는 살짝 다르지만 문제 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
 
class R
{
    private:
        int *data;
    public:
        R(int len)
        {
            this->data = new int[len];
            std::printf("R() call! : new int[%d]\n",len);
        }
        ~R()
        {
            delete []this->data;
            std::printf("~R() call! : delete [] \n");
        }
};
 
void function(void)
{
    R * r = new R(1000);
 
    std::printf("function() 함수 종료\n");
    return;
}
 
int main(void)
{
    function();
    return 0;
}
cs

위의 코드에서 function 함수라는 지역 scope 내에서 객체 R을 동적할당하고, function 함수를 그냥 종료하는 모습이다. 동적 할당된 R 은 소멸되지 않고 그대로 남다가 해당 프로세스가 종료될 때, 비로소 운영체제에 의해서 메모리 자원이 회수된다. 이런 회수를 원한게 아닌데 말이다.

그렇다면 실행 결과를 보자.

분명히, 소멸자가 호출되지 않은 모습이다. 이것이 바로 메모리 누수(Leak)

스마트 포인터

이를 해결할 방법으로 스마트 포인터라는 것이 등장한다. 포인터 역할을 하는 객체로 이 스마트 포인터를 스택 객체로 할당하고 동적 할당 주소를 넘겨주면 스마트 포인터 객체가 소멸될 때 동적 할당된 힙까지 알아서 똑똑하게 삭제를 해주기 때문이다.

이러한 스마트 포인터를 직접 구현해보았다. 다른 이미 구현된 스마트 포인터만큼 스마트하지는 못해서 그냥 오토 포인터라고 이름을 지었다ㅋㅋㅋ

뭐 아래의 연산자 오버로딩 포스팅 때, 구현을 이미 해봤지만 그래도 새롭게 참고하면서 구현해보았다.

 

21 연산자 오버로딩

연산자 오버로딩 연산자 오버로딩 C++언어에서는 연산자의 오버로딩을 통해서 기존에 존재하던 연산자의 기본 기능 이외에 다른 기능을 추가할 수 있다. 먼저 15번 라인을 통해서 operator+ 라는 함

typingdog.tistory.com

코드 및 실행결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
 
class R
{
private:
    int* data;
    int len;
public:
    R(int _len)
    {
        this->len = _len;
        this->data = new int[_len];
        for (int i = 0; i < _len; i++)
            this->data[i] = i * 100000;
        std::printf("R() call! : new int[%d]\n", _len);
    }
    ~R()
    {
        delete[]this->data;
        std::printf("~R() call! : delete [] \n");
    }
    void ShowData(voidconst
    {
        for (int i = 0; i < this->len; i++)
            std::printf("%d, "this->data[i]);
        std::cout << std::endl;
        return;
    }
};
 
template <typename T>
class AutoPointer
{
private:
     const T* ptr;
public:
    AutoPointer(T* _ptr) : ptr(_ptr)
    {
    }
    ~AutoPointer()
    {
        delete this->ptr;
    }
    const T& operator* () const
    {
        return *(this->ptr);
    }
    const T* operator-> () const
    {
        return this->ptr;
    }
};
 
void function(void)
{
    AutoPointer<R> r(new R(10));
    r->ShowData();
    std::printf("function() 함수 종료!\n");
    return;
}
 
int main(void)
{
    function();
    return 0;
}
cs
힙 영역의 객체의 소멸까지 책임지는 스마트 포인터 똑똑해^^7

이렇게 힘들게 만들었는데 이미 C++ 11에서는 다음과 같은 문법을 제공한다.

 

unique_ptr<> : #include <memory>

이미 11 문법에서는 겁나 스마트한 포인터 객체로 제공을 해주었던 것이다... 사용 방법은 내가 만든 오토 포인터와 같으니 바로 예제로 들어가보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <memory>
 
class R
{
    private:
        int *data;
    public:
        R(int len)
        {
            this->data = new int[len];
            std::printf("R() call! : new int[%d]\n",len);
        }
        ~R()
        {
            delete []this->data;
            std::printf("~R() call! : delete [] \n");
        }
};
 
void function(void)
{
    std::unique_ptr<R> r(new R(1000));//R * r = new R(1000);
    std::printf("function() 함수 종료\n");
    return;
}
 
int main(void)
{
    function();
    return 0;
}
cs

불안정한 메모리 관리를 이러한 패턴으로 처리한다는게 정말 잔머리를? 기가막히게 잘 굴린듯한 느낌이다. 그래도 GC(가비지 컬렉터)가 제공되면 참 좋겠지만 이렇게라도 일부라도 관리를 틈틈이 하는게 좋은 것 같다.

다음 번 포스팅에서는 (공부하다가 필요한 부분부분 정리하는 포스팅이기 때문에 시리즈물처럼 바로 다음 번은 아니지만) 이 unique_ptr 외에 공유 가능한, 그리고 인자로의 전달 시 어떤 형태로 코드를 짜야하는 지 등에 대해서 올려볼 것이다.

728x90
반응형
728x90
반응형

오늘은 C++에서의 쓰레드이다

지겹게 기억을 더듬어가면서 C기반의 포스팅을 했던 적이 있는데 C++에서도 지원을 한다니 새로웠다.

 

C기반 I/O Multithreading - 12. 멀티 프로세싱? 멀티 쓰레딩?

멀티 프로세싱에 이어서, 멀티 프로세싱의 단점이 보완되는 멀티 쓰레딩 개념이다. 사실 단점이 보완되기는 하는데 함께 딸려오는 문제 거리도 만만치 않기 때문에 좀 상세히 볼 필요가 있다ㅋ

typingdog.tistory.com

위 링크는 쓰레드

C++ 에서의 쓰레드

원래 쓰레드의 생성과 사용은 OS에 종속적이었고, API가 C 기반이었다. 하지만 C++ 11 표준에 쓰레드 라이브러리가 따로 들어가면서 OS에 독립적이고, 프로그래밍 언어 차원에서 지원이 된다.

임계 영역과 관련된 문제는 Mutex를 통해서 상호배제를 처리할 것인데 이 또한 C++ 프로그래밍 언어 차원에서 지원이 된다는 것이다. 그래서 C 기반의 코드를 그대로 C++ 코드로 변경하도록 해보겠다. 

일단 간단하게 쓰레드 사용과 그에 동반하는 문제까지 설명하자면,

공용 데이터 영역의 변수(전역 변수)

쓰레드에서 공통으로 접근할 수 있는 데이터 영역의 변수를 두 쓰레드에서 임계영역에서 사용이 되면서 기대하는 값과 다른 결과가 나오는 것에서부터 문제가 된다.

쓰레드 A와 쓰레드 B에 해당하며 동일한 횟수만큼(백만) 증감

쓰레드 A에서는 동일한 횟수만큼 (1,000,000) 공용 Data 영역 변수를 1씩 증가시키고, 쓰레드 B에서는 동일한 횟수만큼 (1,000,000) 공용 Data 영역 변수를 1씩 감소시킨다.

기대하는 값은 0 이겠지만(동일한 횟수만큼 1씩 증가시키고, 감소시켰기 때문) 쓰레드 동기화가 이루어지지 않은 탓에 0이 아닌 다른 값이 나온다.

문제에 대한 자세한 설명은 아래의 링크를 통해서 확인하면 된다.

 

C기반 I/O Multithreading - 14. 쓰레드의 치명적인 문제점

C기반 I/O MultiThreading - 12. 멀티 프로세싱? 멀티 쓰레딩? 멀티 프로세싱에 이어서, 멀티 프로세싱의 단점이 보완되는 멀티 쓰레딩 개념이다. 사실 단점이 보완되기는 하는데 함께 딸려오는 문제 거

typingdog.tistory.com

C언어 기반 쓰레드 구현(Mutex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
 
pthread_mutex_t mutex;
int common_value = 0;
 
void* t_main_plus(void *arg);
void* t_main_minus(void *arg);
 
int main(void)
{
    pthread_t tid1, tid2;
 
    pthread_mutex_init(&mutex, NULL);
 
    pthread_create(&tid1, NULL, t_main_plus, NULL); // 쓰레드 생성
    pthread_create(&tid2, NULL, t_main_minus, NULL); // 쓰레드 생성
 
    pthread_detach(tid1); // tid1 에 해당하는 쓰레드가 종료됨과 동시에 소멸.
    pthread_detach(tid2); // tid2 에 해당하는 쓰레드가 종료됨과 동시에 소멸.
 
    sleep(7); // 종료되지 않도록 대기.
    pthread_mutex_destroy(&mutex);
    printf("메인함수가 종료됩니다. [common_value의 최종 값 : %d]\n",common_value);
    return 0;
}
 
void* t_main_plus(void *arg)
{
    int i = 0;
 
    printf("t_main_plus 쓰레드가 연산을 시작합니다. \n");
 
    pthread_mutex_lock(&mutex);
    for(i=0; i<1000000; i++// for을 중복해서 쓴 것은 100번 type을 검사하는 것보단 났다고 생각.
        common_value+=1;
    pthread_mutex_unlock(&mutex);
 
    printf("t_main_plus 쓰레드가 종료됩니다.\n");
    return NULL;
}
 
 
void* t_main_minus(void *arg)
{
    int i = 0;
 
    printf("t_main_minus 쓰레드가 연산을 시작합니다. \n");
 
    pthread_mutex_lock(&mutex);
    for(i=0; i<1000000; i++// for을 중복해서 쓴 것은 100번 type을 검사하는 것보단 났다고 생각.
        common_value-=1;
    pthread_mutex_unlock(&mutex);
 
    printf("t_main_minus 쓰레드가 종료됩니다.\n");
    return NULL;
}
cs

C++언어 기반 쓰레드 구현(Mutex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>
#include <mutex>
 
std::mutex mtx;
int comm_value = 0;
 
int main(void)
{
    std::thread t1([]() { mtx.lock(); for (int i = 0; i < 1000000; i++)    comm_value--; mtx.unlock(); });
    std::thread t2([]() { mtx.lock();  for (int i = 0; i < 1000000; i++)    comm_value++; mtx.unlock(); });
 
    t1.join();
    t2.join();
 
    std::cout << "comm_value의 값은 : " << comm_value << std::endl;
    return 0;
}
cs

 

위의 코드가 아래의 코드로 변환되었다고 생각하면 된다. 너무 간단하게 몇 줄이면 끝났다ㅋㅋㅋㅋ

끝.

728x90
반응형
728x90
반응형

std::function이 필요한 이유부터 설명하겠다.

위는 함수 포인터로 객체의 멤버함수를 가리킬려고 온갖 발악을 하지만.. 되지 않는 광경을 보고 있다. 35번 줄은 말도 안되기 때문에 에러부터 난다.

왜 에러가 날까?

1. = 연산자를 기준으로 타입이 맞지 않다.

2. C++에서는 객체의 멤버 함수의 이름은 해당 함수의 주소 값으로 변환되지 않는다. 즉, 객체 멤버 함수의 이름으로는 함수의 주소 값을 알 수 없다는 것이다.

3. 설령, 주소를 어떻게 넣는다고 하더라도, 해당 주소의 함수가 어떤 객체의 것인지 알 수가 없다.

뭐 아무튼 그래서 36번 라인과 40번 라인처럼 기이한 방법으로 사용한다... 나의 의도가 아니다 ㅋㅋㅋㅋㅋ

이런 설정인데, 예로 보겠다.

예 중 하나인데, 번호 순서대로 읽으면 되며, 배열 형태로도 가능하며, 단일 형태로도 할당이 가능하다.

위의 예를 호출하는 문장이다.

아래와 같이 객체를 () 연산자 오버로딩한 객체, functor 또한 std::function으로 담아낼 수 있다.

 

그리고 또한 멤버 함수 또한 받을 수 있는데 이 또한 예제로 보겠다.

 

아래에 전체 코드와 실행 결과를 확인해보겠다. 먼저, 함수 포인터를 이용한 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
 
using namespace std;
 
class A
{
        private:
                int n;
        public:
                A(int a):n(a)
                {
                }
                void memberFunc(void)
                {
                        cout<<"n:"<<this->n<<endl;
                        return;
                }
};
 
void normalFunc(void)
{
        cout<<"call()!!"<<endl;
        return;
}
 
int main(void)
{
        A a(100);
 
        void (*nfunc)(void);
        void (*mfunc)(void);
        void (A::*mmfunc)(void);
 
        nfunc = normalFunc;
        // mfunc = a.memberFunc;
        mmfunc = &A::memberFunc;
 
        nfunc();
        // mfunc();
        (a.*mmfunc)();
 
        return 0;
}
cs

다음은 std::function에 대한 전체 예제이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <functional>
#include <string>
#include <iostream>
 
class A
{
        private:
                std::string name;
        public:
                A(const char * _name)
                {
                        this->name = _name;
                }
                void AFunc()
                {
                        std::cout<<"AFunc() Call! name : "<<this->name<<std::endl;
                        return;
                }
};
 
struct S
{
        void operator()(const std::string& str)
        {
                std::cout<<"S::operator() Call! : "<<str<<std::endl;
                return;
        }
};
 
int main(void)
{
        A a("Kim");
        A b("Yoo");
 
        int m = 100;
 
        std::function<int(intint)> operators[] =
        {
                [](int n1, int n2) -> int { return n1 + n2; },
                [](int n1, int n2) -> int { return n1 - n2; },
                [](int n1, int n2) -> int { return n1 * n2; },
                [](int n1, int n2) -> int { return n1 / n2; },
                [](int n1, int n2) -> int { return n1 % n2; },
                [m](int num1, int num2) -> int { return m*num1 + m*num2; },
        };
 
        std::function<void(const std::string& str)> f1 = S();
 
        std::function<void(A&)> FirstNameFunc = &A::AFunc;
 
        std::cout<<"operators[0] : "<<operators[0](55)<<std::endl;
        std::cout<<"operators[1] : "<<operators[1](55)<<std::endl;
        std::cout<<"operators[2] : "<<operators[2](55)<<std::endl;
        std::cout<<"operators[3] : "<<operators[3](55)<<std::endl;
        std::cout<<"operators[4] : "<<operators[4](55)<<std::endl;
        std::cout<<"operators[5] : "<<operators[5](55)<<std::endl;
 
        std::string str = "Hi Hello~";
        f1(str);
        FirstNameFunc(a);
        FirstNameFunc(b);
 
        return 0;
}
cs

728x90
반응형
728x90
반응형

C++에서도 자바스크립트 처럼 익명 함수, 클로져 쓰듯, 사용할 수 있다는게 웹 개발을 해봤던 나로서는 너무 신기하고 놀라웠다. 함수처럼 사용되는 객체 등으로 이미 지원이 되긴 했지만, 

위와 같이 준비를 해야 사용이 가능하므로 불편하긴 마찬가지긴 하다. ( 다른 언어에 비하면 )

자 그럼, 람다 표현식은 다음과 같다.

형식은 위와 같은데, 캡처라는 부분이 사실 처음에 잘 와닿지 않았다. 그런데 자세히 보니, 캡처란? 람다 표현식의 바디 내에서 사용할 외부 변수를 지정해주는 구간일 뿐이었다.

바로 예시를 들겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <string>
 
int content = 1;
 
int main(void)
{
    std::string str = "IOSTREAM";
    int zero = 0;
    int plus999 = 999;
 
    std::cout << [zero, plus999, str](int a, int b) -> int { 
        std::cout << str << std::endl; content = 400return ((a + b) * zero + plus999); 
    }(5149<< std::endl;
    std::cout << "[zero, plus999, str] content = " << content << std::endl;
 
    std::cout <<  [=](int a, int b) -> int {
        std::cout << str << std::endl; content = 200return ((a + b) * zero + plus999);
    }(5149<< std::endl;
    std::cout << "[=] content = " << content << std::endl;
 
    std::cout <<  [&](int a, int b) -> int {
        std::cout << str << std::endl; content = 400return ((a + b) * zero + plus999);
    }(5149<< std::endl;
    std::cout << "[&] content = " << content << std::endl;
 
    return 0;
}
cs

728x90
반응형
728x90
반응형

몸살 감기로 아팠다. 그래서 기념으로 좀 쉬고, 일어나서 포스팅한다ㅠ

먼저, C++ 언어에서의 L-Value와 R-Value에 대해서 정리할 필요가 있다. 너무나도 당연하다고 생각해왔던 것들을 새삼스럽게 정리를 하려고 보니 좀 어려운 면이 없지 않아 있다.

L-Value와 R-Value란?

위의 그림에서와 같이 대입 연산자를 기준으로 왼쪽의 특성을 갖는 요소는 무조건 L Value라고 한다. 이 말은 오른쪽에 위치해도 L Value라고 할 수 있다는 소리다. 그 반대로 대입 연산자를 기준으로 오른쪽의 특성을 갖는 요소는 무조건 R Value라고 한다. 그러나 R Value는 대입 연산자 기준으로 왼쪽에 올 수 없다.

아마 특징과 예시를 보면 L Value와 R Value가 무엇인지 알 수 있을 것이다.

L-Value와 R-Value의 특징

먼저, 그 특징이다.

L value는 보통 변수를 대부분의 예로 들 수 있으며, R value는 임시 객체 혹은 주소 +, - 등의 연산 결과 등이 있다.

그런데 C++에서는 R Value에서 임시 객체라고 하는 것들, 요 부류에 해당하는 것들이 꼭 문제가 된다. 왜냐하면 다음과 같은 경우를 보자.

R-Value에서 문제가 되는 부분

대충 이러한 클래스가 있다.

이 클래스를 기반으로 여러 가지 생성과 동시에 초기화를 할 것이다. 그 생성과 동시에 초기화를 할 때, 두 가지 경우를 보자.

1번의 설명대로, 생성과 동시에 초기화하는 두 경우는 문법 상, 복사 생성자가 호출되는 것이 맞다. 왜냐하면 초기화할 값의 대상으로 객체가 넘어오기 때문이다. 위의 클래스에서처럼 인스턴스화 할 때 동적 할당을 해야하는 경우에는 깊은 복사가 필수이므로, 복사 생성자가 호출되어야 한다.

그런데 자세히 보면 효율적이지 못한 부분이 있다.

AAA b(AAA("ccc")); 이 문장의 경우에는 분명 괄호 안의 AAA("ccc")는 R value 로 임시 객체이며 곧 사라진다. 그렇기 때문에 b 내부의 str을 또 동적 할당하고~, 임시 객체의 str 내용을 또 b의 str에 복사하는 이런 작업을 하지 말고, 2번 처럼 그냥 임시 객체의 str의 주소 값을 b의 str 에 넣어버리고 임시 객체는 그냥 소멸해버리면 되는 것 아니냐?? 어차피 사라질 놈이니까

만약에 깊은 복사가 이루어지는 것이 뭐 단순 str 같은 것들이 아니라 100MB 정도되는 동영상이라고 생각해보자. 객체 하나 복사하는데 100MB 짜리가 막 두개 였다가 하나였다가 메모리가 아주 들쑥날쑥 할 것이다.

그러나 실제로는 2번과 유사하긴 하지만 조금 다르게 복사 생략(copy elision)이라는 현상이 발생한다.

 

또한 이런 경우도 존재한다.

위와 같은 경우도 결국 s 혹은 z 객체는 곧 사라질 객체이기 때문에 새로 생성되는 임시 객체에 값을 전달할 때, 복사 생성자가 호출된다. 

위 경우들 모두 어차피 사라질 객체들이므로, 객체 내의 깊은 복사가 필요한 부분은 그냥 사라질 객체 내의 str 변수의 메모리 주소만 보존하여 가지고 와서 갱신해주고, 객체는 소멸 시켜버리면 된다.

그래서 나온 개념이 r-value 레퍼런스이동 생성자이다.

r-value 레퍼런스, 이동 생성자

205라인은 const & 참조자를 이용해 예외적으로 R-value를 잡을 수 있었다. 그러나 [11]부터는 211 라인에서 처럼 r - va lue를 잡을 수 있는 r value 레퍼런스가 등장했는데, 이를 이용하여 r-value가 해당 라인이 종료된 후에도 소멸되지 않고 잡아둘 수 있을 뿐만 아니라, 값의 변경까지 허용한다!

이를 이용하여 만들어진 것이 이동 생성자이다!

이동 생성자와 복사 생성자를 함께 보도록 하자.

위의 이동 생성자로 인해 다음과 같이 복사 생성자의 호출을 막고, 메모리가 새로 할당되고, 복사하고 이런 과정을 막았다. 이는 매우 효율적이다!

결론적으로 이동 생성자는 다음과 같이 보면 된다.

C++ 11에서는 바로 이러한 이동 생성자라든가, R value 레퍼런스 등의 문법 덕분에 자원 문제가 해결되었다. 아래의 내용을 기록하려고 위를 기록한 것이다 결국..

STL 컨테이너 등을 사용할 때, 위처럼 대입하는데에만 복사 생성자가 2번 넘게 호출되며 메모리가 들쑥날쑥할텐데 이동 생성자라든가, 복사 생략 등으로 인해서 자원 효율이 좋아진다는 점이다.

RVO & NRVO

RVORetrun Value Optimization 의 약자이고, NRVONamed Return Value Optimization의 약자이다.

뭔가 리턴 값을 최적화 한다는 소리인 것 같은데 다음의 예제를 보자.

위의 두 경우의 함수가 있다. 앞에는 컴파일러에 의해 NRVO 가 적용되는 함수이고, 뒤에는 컴파일러에 의해 RVO가 적용되는 함수이다.

이를 호출할 경우, 두 경우 반환하는 형태가 모두 참조형으로의 반환이 아니기 때문에 복사 생성자가 호출된다!

그러나 실제로는 그렇지 않다.

그렇다고 해서 이동 생성자가 호출된 것도 아니고, 실제로 이동 생성자가 호출되지도 않는다

RVO, NRVO는 반환되는 값에 대해서 최적화하여 복사 생성자가 호출이 되지 않게 하는 것이다. 이동 생성자나 복사 생성자보다 더 우선순위 있게 실행이 된다. 당연하게도 최적화기 때문에 우선순위가 높은 것이다.

그러면 위에서 이동 생성자가 호출된 NRVO 함수 호출은 무엇이었을까?

위의 예시처럼 NRVO가 실행이 안 되는 경우에는 이동 생성자가 호출되는 것이다. 

근데 이게 컴파일러마다 조금씩 다르다 VS 2019 와 g++ 컴파일러에서는 다르게 표현되는 경우도 있고 한 것 같다. 하 이게 정말 별거 아닌 개념인 것 같은데 컴파일러마다 다르고, 이미 올라와있는 11 내용이랑 17, 20에서 바뀐 표준안이랑 살짝 충돌되는 경우들도 있어서 알아보느라 너무 힘들었다 정말.

아래는 사용한 코드이다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#include <iostream>
#include <vector>
#include <cstring>
#pragma warning(disable:4996)
 
class AAA
{
private:
    char* data;
public:
    AAA(const char* data)
    {
        this->data = new char[1000000];
        std::strcpy(this->data, data);
        std::cout << "Call AAA(char * data)![data:" << this->data << "] / this : [" << this << ']' << std::endl;
    }
    AAA(const AAA& ref)
    {
        this->data = new char[1000000];
        std::strcpy(this->data, ref.data);
        std::cout << "Call AAA(const AAA& ref)![data:" << this->data << "] / this : [" << this << ']' << std::endl;
    }
    AAA(AAA&& ref) : data(ref.data)
    {
        ref.data = nullptr;
        std::cout << "Call AAA(AAA&& ref)![data:" << this->data << "] / this : [" << this << ']' << std::endl;
    }
    ~AAA()
    {
        if (this->data != nullptr)
        {
            std::cout << "Call ~AAA()![data:" << this->data << "] / this : [" << this << ']' << std::endl;
            delete[]this->data;
        }
        else
        {
            std::cout << "Call ~AAA()! / this : [" << this << ']' << std::endl;
        }
    }
 
    void ShowData(void)
    {
        if (this->data == nullptr)
            std::cout << "data : " << std::endl;
        else
            std::cout << "data : " << this->data << std::endl;
    }
};
 
AAA FuncNrvo(void)
{
    AAA s("ssssssssssssssssssssssssssssss");
    return s;
}
AAA FuncNrvoS(int a)
{
    AAA s("ssssssssssssssssssssssssssssss");
    AAA z("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
    if (a == 100)
    {
        return s;
    }
    else
    {
        return z;
    }
}
 
AAA FuncRvo(void)
{
    return AAA("ssssssssssssssssssssssssssssss");
}
AAA FuncRvoS(int a)
{
    if (a == 100)
    {
        return AAA("ssssssssssssssssssssssssssssss");
    }
    else
    {
        return AAA("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
    }
}
 
int main(void)
{
    std::cout << "1. 일반 생성자 호출" << std::endl;
    {
        AAA a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        AAA b("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "2. 복사 생성자 호출" << std::endl;
    {
        AAA a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        AAA b("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
 
        AAA c = a;
        AAA d(b);
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "3. 임시 변수 r-value의 생성 - 라인이 끝나면 소멸된다." << std::endl;
    {
        AAA("cccccccccccccccccccccccccccccc");
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "4. l value 에 r value 대입 - 생성과 동시에 초기화 및 라인이 끝났음에도 소멸되지 않음." << std::endl;
    {
        AAA e(AAA("dddddddddddddddddddddddddddddd"));
        std::cout << "&e : " << &<< std::endl;
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "5. r-value를 const & 를 통해서 가리킴 - 라인이 끝났음에도 소멸되지 않음." << std::endl;
    {
        const AAA& f(AAA("eeeeeeeeeeeeeeeeeeeeeeeeeeeeee"));
        std::cout << "&f : " << &<< std::endl;
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "6. FuncNrvo() 함수의 AAA 반환으로 생성과 동시에 초기화." << std::endl;
    {
        AAA g(FuncNrvo());
        std::cout << "&g : " << &<< std::endl;
        g.ShowData();
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "7. FuncNrvoS() 함수의 AAA 반환으로 생성과 동시에 초기화." << std::endl;
    {
        AAA h(FuncNrvoS(11));
        std::cout << "&h : " << &<< std::endl;
        h.ShowData();
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "8. l-value를 r-value화 하여 생성과 동시에 초기화." << std::endl;
    {
        AAA a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
 
        AAA i(std::move(a));
        std::cout << "&i : " << &<< std::endl;
        i.ShowData();
        a.ShowData();
 
        FuncNrvo();
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "10. 그냥 FuncNrvoS()만 호출" << std::endl;
    {
        FuncNrvoS(19);
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
 
    std::cout << "11. 그냥 FuncRvo()만 호출" << std::endl;
    {
        FuncRvo();
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "12. 그냥 FuncRvoS()만 호출" << std::endl;
    {
        FuncRvoS(19);
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
 
    std::cout << "13.FuncRvo()을 받으며 호출" << std::endl;
    {
        AAA j(FuncRvo());
        std::cout << "&j : " << &<< std::endl;
 
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    std::cout << "14. FuncRvoS()을 받으며 호출" << std::endl;
    {
        AAA k(FuncRvoS(19));
        std::cout << "&k : " << &<< std::endl;
 
        std::cout << "라인이 넘어감" << std::endl;
    }
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
    // 복사 생성자를 호출하지 않으며, AAA(100)의 임시객체는 즉시 소멸하지도 않는다.
    // -> e 자체를 AAA(100)으로 만들어진 객체로 취급
    std::cout << "--- --- --- --- --- --- --- --- --- --- --- --- ---" << std::endl;
 
    const int& lref = 100;
    //lref = 101;
    std::cout << lref << std::endl;
 
    std::cout << "--------------------------" << std::endl;
 
    int&& rref = 100;
    rref = 101;
    std::cout << rref << std::endl;
 
    return 0;
}
cs
728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

함수 포인터를 대체하는 std::function  (0) 2021.02.08
Lambda Expression  (0) 2021.01.27
자료형 추론 auto / 범위 기반 for  (0) 2021.01.21
Iterator 사용 방법 예시  (0) 2021.01.05
Priority Queue  (0) 2021.01.04
728x90
반응형

 

다음과 같은 배열들을 순회할 것인데, 

1
2
3
4
int numArr[10= { 95732923, };
std::vector<int> numVec(&numArr[0], &numArr[5]);
const char* strArr[3= { "I'm Shyoo""Dog""Mouse" };
 
cs

전통적인(?), 일반적인 순회 방법과 auto 자동 추론을 이용한 반복과 범위 기반 반복을 각각 실험해본다.

일반적인 순회

1
2
3
4
5
6
7
8
9
10
11
12
13
cout << "[기존 배열 순회 방법]" << endl;
 
for (int i = 0; i < 10; i++)
    cout << numArr[i] << ' ';
cout << endl;
 
for (std::vector<int>::iterator it = numVec.begin(); it != numVec.end(); it++)
    cout << *it << ' ';
cout << endl;
 
for(char ** p = (char **)strArr; p <= strArr+2; p++// 억지로 계산하는게 꺼림칙하긴 하지만..
    cout << *<< ' ';
cout << endl;
cs

일반적인 배열, 벡터, 포인터 배열을 순회하는 평범한 코드이다.

자료형 자동 추론을 통한 순회

1
2
3
4
5
6
7
8
9
10
11
12
13
cout << "[자료형 자동 추론]" << endl;
 
for (auto i = 0; i < 10; i++)
    cout << numArr[i] << ' ';
cout << endl;
 
for (auto it = numVec.begin(); it != numVec.end(); it++)
    cout << *it << ' ';
cout << endl;
 
for (auto p = (char**)strArr; p <= strArr + 2; p++)
    cout << *<< ' ';
cout << endl;
cs

auto 키워드를 통해서 인덱스 역할을 하는 변수의 자료형을 알아서 탐지한다.. 너무 편하다 정말

범위 기반 반복 for을 통한 순회

1
2
3
4
5
6
7
8
9
10
11
12
13
cout << "[범위 기반 for문을 이용한 배열 순회]" << endl;
 
for (auto element : numArr)
    cout << element << ' ';
cout << endl;
 
for (auto it : numVec)
    cout << it << ' ';
cout << endl;
 
for(auto p : strArr)
    cout << p << ' ';
cout << endl;
cs

다른 언어에서 지원하는 것처럼 c++도 지원한다. 자바스크립트나 파이썬의 for in과 같고, 자바의 for each 구조와 같다.

 

전체 코드 및 실행 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <vector>
 
using std::cout;
using std::endl;
using std::cin;
 
int main(void)
{
    int numArr[10= { 95732923, };
    std::vector<int> numVec(&numArr[0], &numArr[5]);
    const char* strArr[3= { "I'm Shyoo""Dog""Mouse" };
 
    cout << "[기존 배열 순회 방법]" << endl;
    for (int i = 0; i < 10; i++)
        cout << numArr[i] << ' ';
    cout << endl;
 
    for (std::vector<int>::iterator it = numVec.begin(); it != numVec.end(); it++)
        cout << *it << ' ';
    cout << endl;
 
    for(char ** p = (char **)strArr; p <= strArr+2; p++// 억지로 계산하는게 꺼림칙하긴 하지만..
        cout << *<< ' ';
    cout << endl;
 
 
    cout << "[자료형 자동 추론]" << endl;
    for (auto i = 0; i < 10; i++)
        cout << numArr[i] << ' ';
    cout << endl;
 
    for (auto it = numVec.begin(); it != numVec.end(); it++)
        cout << *it << ' ';
    cout << endl;
 
    for (auto p = (char**)strArr; p <= strArr + 2; p++)
        cout << *<< ' ';
    cout << endl;
 
    cout << "[범위 기반 for문을 이용한 배열 순회]" << endl;
 
    for (auto element : numArr)
        cout << element << ' ';
    cout << endl;
 
    for (auto it : numVec)
        cout << it << ' ';
    cout << endl;
 
    for(auto p : strArr)
        cout << p << ' ';
    cout << endl;
 
 
    return 0;
}
cs

실행이 기가 막히게 잘되는 것을 볼 수 있다.

 

728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

Lambda Expression  (0) 2021.01.27
R-Value, Copy Elision, 이동 생성자, RVO, NRVO  (0) 2021.01.26
Iterator 사용 방법 예시  (0) 2021.01.05
Priority Queue  (0) 2021.01.04
Queue  (0) 2021.01.04
728x90
반응형
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
#include <vector>
 
using namespace std;
 
int main(void)
{
    // vector 값 10개를 추가한다.
    vector<int> vec;
    for (int i = 0; i < 10; i++)
        vec.push_back(i);
 
    // iterator를 이용한 순회
    vector<int>::iterator it;
    for (it = vec.begin(); it != vec.end(); it++)
        cout << *it << endl;
    cout << endl;
 
    // iterator를 포인터처럼 사용할 수 있다.
    it = vec.begin();
    cout << it[3<< endl// 3
    cout << *(it+3<< endl// 3
    cout << *(++it) << endl// 1
    cout << *it++ << endl// 1
    cout << *it << endl// 2
 
    it[0= 100;
    cout << it[0<< endl// 100
    
    *(it+1= 200;
    cout << *(it + 1<< endl// 200
    cout << it[1<< endl// 200
 
    cout << "--------------------------------------------------" << endl;
    vector<int>::const_iterator cit = vec.begin();
    for (cit = vec.begin(); cit != vec.end(); cit++)
        cout << *cit << endl;
    cout << "--------------------------------------------------" << endl;
    cout << cit[-1<< endl;
    cout << cit[-2<< endl;
    cout << cit[-3<< endl;
    cout << cit[-4<< endl;
    cout << cit[-5<< endl;
    cout << cit[-6<< endl;
    cout << cit[-7<< endl;
    cout << cit[-8<< endl;
    cout << cit[-9<< endl;
    cout << cit[-10<< endl;
    // cout << cit[-11] << endl; // 에러 ㅋ
    cout << "--------------------------------------------------" << endl;
    // vec.begin() 실제 요소, vec.end() 요소가 아님 -> 가리킬 수 없음
 
    // 값이 반전되어 출력함
    vector<int>::reverse_iterator rit;
    for (rit = vec.rbegin(); rit != vec.rend(); rit++)
        cout << *rit << endl;
 
    cout << "--------------------------------------------------" << endl;
    vector<int>::const_reverse_iterator crit;
    for (crit = vec.crbegin(); crit != vec.crend(); crit++)
        cout << *crit << endl;
 
    return 0;
}
cs
728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

R-Value, Copy Elision, 이동 생성자, RVO, NRVO  (0) 2021.01.26
자료형 추론 auto / 범위 기반 for  (0) 2021.01.21
Priority Queue  (0) 2021.01.04
Queue  (0) 2021.01.04
Stack  (0) 2021.01.03
728x90
반응형

 

우선 순위 큐란?

Heap 자료 구조를 내부적으로 사용한다.
기본적으로 값이 클수록(문자의 경우 사전 상 뒤의 순서일수록) 우선순위가 높다. <- 이는 우선 순위를 정하기 나름이다.

이렇게 나올줄 알고 이미 c언어로 구현을 해보았다ㅋ 매우

https://typingdog.tistory.com/110

 

C Data Structure - Heap ( Priority Queue, 우선 순위 큐 ) 개념 및 코드 구현

공부를 하고 정리하는 것이 매우 중요하다. 근데 그 정리를 하기가 조금 귀찮은 것이 아니다. 앞으로는 그 때 그 때 정리하도록 하고, 모아두지 말아야겠다.. 업로드 할 것이 한 두 가지가 아니다

typingdog.tistory.com

 

코드 및 실행 결과

----

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <queue>
#include <iostream>
#include <string>
 
using namespace std;
 
int main(void)
{
    priority_queue<string> pq;
    string words[3= {
        "Apple",
        "Banana",
        "Corn"
    };
    for (int i = 0; i < 3; i++)
        pq.push(words[i]);
    while (!pq.empty())
    {
        cout << pq.top() << endl;
        pq.pop(); // 분명히 top 에서 Pop 으로 뽑아내지만 선입 선출의 순서가 아닌 우선 순위대로 값을 뽑아낸다. 
        // 내부적으로 사용되는 자료 구조 방식이 다르기 때문에.
    }
 
    return 0;
}
cs

----

 

728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

자료형 추론 auto / 범위 기반 for  (0) 2021.01.21
Iterator 사용 방법 예시  (0) 2021.01.05
Queue  (0) 2021.01.04
Stack  (0) 2021.01.03
Tuple  (0) 2021.01.03
728x90
반응형

큐란?

컨테이너를 확장 및 축소하여 기능을 제한하거나 특정 기능과 특징을 사용하도록 만든 것을 컨테이너 어댑터라고 하는데, 큐 또한 바로 그것이다. 

내부적으로 dequeue, list 등의 컨테이너로 동작하며 기본적으로는 dequeue로 동작한다. vector는 안된다! 왜냐하면 vector는 앞에서 값을 빼는 pop 기능이 없기 때문이다.( vector::push_back( )과 vector::pop_back( )만 존재함 )

사용되고 있는 내부 컨테이너를 변경하려면 queue<data type, list<int>> listack; 이러한 식으로 진행하면 된다. ( 스택에서 사용될 data type, 변경할 컨테이너 구조: list or dequeue등)

코드 및 실행 결과

----

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <queue>
#include <iostream>
#include <string>
 
using namespace std;
 
int main(void)
{
    queue<string> qu;
    string words[3= {
        "Apple",
        "Banana",
        "Corn"
    };
 
    for (int i = 0; i < 3; i++)
        qu.push(words[i]);
 
    while (!qu.empty())
    {
        cout << qu.front() << endl;
        qu.pop();
    }
    return 0;
}
cs

----

728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

Iterator 사용 방법 예시  (0) 2021.01.05
Priority Queue  (0) 2021.01.04
Stack  (0) 2021.01.03
Tuple  (0) 2021.01.03
Pair  (0) 2021.01.03
728x90
반응형

스택이란?

컨테이너를 확장 및 축소하여 기능을 제한하거나 특정 기능과 특징을 사용하도록 만든 것을 컨테이너 어댑터라고 하는데, 스택이 바로 그것이다.

내부적으로 vector, dequeue, list 등의 컨테이너로 동작하며 기본적으로는 dequeue로 동작한다.

내부 컨테이너를 변경하려면 stack<data type, list<int>> listack; 이러한 식으로 진행하면 된다. ( 스택에서 사용될 data type, 변경할 컨테이너 구조: list or vector 등)

코드 및 실행 결과

----

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stack>
#include <iostream>
#include <string>
 
using namespace std;
 
int main(void)
{
    stack<string> st;
    string words[3= {
        "Apple",
        "Banana",
        "Corn"
    };
 
    for (int i = 0; i < 3; i++)
        st.push(words[i]);
    
    while (!st.empty())
    {
        cout << st.top() << endl;
        st.pop();
    }
    return 0;
}
cs

----

728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

Priority Queue  (0) 2021.01.04
Queue  (0) 2021.01.04
Tuple  (0) 2021.01.03
Pair  (0) 2021.01.03
Map  (0) 2020.09.15

+ Recent posts