C++/Library 2021. 3. 26. 15:19

set

  • <set> 라이브러리

예제

  • 원소가 중복되지 않는다.

    #include <iostream>
    #include <set>
    
    int            main()
    {
      using namespace std;
    
      set<string> str_set;
    
      str_set.insert("Hello");
      str_set.insert("World");
      str_set.insert("Hello");
    
      cout << "size : " << str_set.size() << endl;
    
      for (auto& e : str_set)
        cout << e << ' ';
      cout << endl;
    }
    
    /* stdout stderr
    size : 2
    Hello World
    */

'C++ > Library' 카테고리의 다른 글

C++ map  (0) 2021.03.26
C++ multiset  (0) 2021.03.26
C++ Associative Containers  (0) 2021.03.26
C++ Priority queue  (0) 2021.03.26
C++ queue  (0) 2021.03.26
C++/Library 2021. 3. 26. 15:18

Associative Containers

set

multiset

map

multimap

'C++ > Library' 카테고리의 다른 글

C++ multiset  (0) 2021.03.26
C++ set  (0) 2021.03.26
C++ Priority queue  (0) 2021.03.26
C++ queue  (0) 2021.03.26
C++ stack  (0) 2021.03.26
C++/Library 2021. 3. 26. 15:17

Priority queue

  • <queue> 라이브러리

기본 예제

  • 원소 간에 우선순위가 존재한다.

  • 템플릿으로 클래스를 사용하려면 비교 연산자 오버로딩을 해야한다.

    #include <iostream>
    #include <queue>
    
    int            main()
    {
      using namespace std;
    
      priority_queue<int> queue;
    
      for (const int n : {1, 8, 5, 6, 3, 4, 0, 9, 7, 2})
        queue.push(n);
    
      for (int i = 0; i < 10; ++i)
      {
        cout << queue.top() << endl;
        queue.pop();
      }
    }
    
    /* stdout stderr
    9
    8
    7
    6
    5
    4
    3
    2
    1
    0
    */

'C++ > Library' 카테고리의 다른 글

C++ set  (0) 2021.03.26
C++ Associative Containers  (0) 2021.03.26
C++ queue  (0) 2021.03.26
C++ stack  (0) 2021.03.26
C++ Container Adaptors  (0) 2021.03.24
C++/Library 2021. 3. 26. 15:17

queue

  • <queue> 라이브러리

기본 예제

  • pop()으로 맨 앞의 원소를 제거한다.

    #include <iostream>
    #include <queue>
    
    int            main()
    {
      using namespace std;
    
      queue<int> queue;
    
      queue.push(1);
      queue.push(2);
      queue.push(3);
    
      cout << queue.front() << ' ' << queue.back() << endl;
      queue.pop();
      cout << queue.front() << ' ' << queue.back() << endl;
    }
    
    /* stdout stderr
    1 3
    2 3
    */

'C++ > Library' 카테고리의 다른 글

C++ Associative Containers  (0) 2021.03.26
C++ Priority queue  (0) 2021.03.26
C++ stack  (0) 2021.03.26
C++ Container Adaptors  (0) 2021.03.24
C++ deque (double-ended queue)  (0) 2021.03.24
C++/Library 2021. 3. 26. 15:16

stack

  • <stack> 라이브러리

기본 예제

  • pop()으로 맨 위의 원소를 제거한다.

    #include <iostream>
    #include <stack>
    
    int            main()
    {
      using namespace std;
    
      stack<int> stack;
    
      stack.push(1);
      stack.emplace(2);
      stack.emplace(3);
      cout << stack.top() << '\n';
      stack.pop();
      cout << stack.top() << '\n';
    }
    
    /* stdout stderr
    3
    2
    */

멤버 함수

  • push()

    • 값을 복사해서 넣는다.
  • emplace()

    • 원소를 새로 생성해서 넣기 때문에 복사 생성자나 이동 생성자가 호출되지 않는다.

'C++ > Library' 카테고리의 다른 글

C++ Priority queue  (0) 2021.03.26
C++ queue  (0) 2021.03.26
C++ Container Adaptors  (0) 2021.03.24
C++ deque (double-ended queue)  (0) 2021.03.24
C++ Sequences Containers  (0) 2021.03.24
C++/Library 2021. 3. 24. 10:55

Container Adaptors

stack

queue

priority queue

'C++ > Library' 카테고리의 다른 글

C++ queue  (0) 2021.03.26
C++ stack  (0) 2021.03.26
C++ deque (double-ended queue)  (0) 2021.03.24
C++ Sequences Containers  (0) 2021.03.24
C++ STL 컨테이너 (STL Containers)  (0) 2021.03.24
C++/Library 2021. 3. 24. 10:54

deque (double-ended queue)

  • <deque> 라이브러리

기본 예제

  • vector와 비슷하다.

  • push_front로 앞에서부터 추가할 수도 있다.

    #include <iostream>
    #include <deque>
    
    int            main()
    {
      using namespace std;
    
      deque<int> deq;
      for (int i = 0; i < 10; ++i)
      {
        deq.push_front(i);
        deq.push_back(i);
      }
      for (auto& e : deq)
        cout << e << ' ';
      cout << endl;
    }
    
    /* stdout stderr
    9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9
    */

'C++ > Library' 카테고리의 다른 글

C++ stack  (0) 2021.03.26
C++ Container Adaptors  (0) 2021.03.24
C++ Sequences Containers  (0) 2021.03.24
C++ STL 컨테이너 (STL Containers)  (0) 2021.03.24
C++ 표준 템플릿 라이브러리 (STL, Standard Template Libraries)  (0) 2021.03.24
C++/Library 2021. 3. 24. 10:53

Sequences Containers

vector

deque (double-ended queue)

list

slist

'C++ > Library' 카테고리의 다른 글

C++ Container Adaptors  (0) 2021.03.24
C++ deque (double-ended queue)  (0) 2021.03.24
C++ STL 컨테이너 (STL Containers)  (0) 2021.03.24
C++ 표준 템플릿 라이브러리 (STL, Standard Template Libraries)  (0) 2021.03.24
C++ weak_ptr  (0) 2021.03.24
C++/Library 2021. 3. 24. 10:51

STL 컨테이너 (STL Containers)

Simple Containers

  • pair

Sequences Containers

Container Adaptors

Associative Containers

Other types of Containers

  • bitset

  • valarray


참고

'C++ > Library' 카테고리의 다른 글

C++ deque (double-ended queue)  (0) 2021.03.24
C++ Sequences Containers  (0) 2021.03.24
C++ 표준 템플릿 라이브러리 (STL, Standard Template Libraries)  (0) 2021.03.24
C++ weak_ptr  (0) 2021.03.24
C++ shared_ptr  (0) 2021.03.24
C++/Library 2021. 3. 24. 10:50

표준 템플릿 라이브러리 (STL, Standard Template Libraries)

Standard Library와 STL의 차이

  • STL은 아래 4가지로 구성되어있고, 나머지는 그냥 Standard Library이다.

STL 컨테이너 (Containers)

STL 반복자 (Iterators)

STL 알고리즘 (Algorithms)

Functions

'C++ > Library' 카테고리의 다른 글

C++ Sequences Containers  (0) 2021.03.24
C++ STL 컨테이너 (STL Containers)  (0) 2021.03.24
C++ weak_ptr  (0) 2021.03.24
C++ shared_ptr  (0) 2021.03.24
C++ unique_ptr  (0) 2021.03.24
C++/TBC++ 2021. 3. 24. 10:49

따라하며 배우는 C++ 16장

표준 템플릿 라이브러리 (Standard Template Libraries)


'C++ > TBC++' 카테고리의 다른 글

따라하며 배우는 C++ 18장  (0) 2021.03.26
따라하며 배우는 C++ 17장  (0) 2021.03.26
C++ 따라하며 배우는 C++ 15장  (0) 2021.03.22
C++ 따라하며 배우는 C++ 14장  (0) 2021.03.22
따라하며 배우는 C++ 13장  (0) 2021.03.21
C++/SmartPointer 2021. 3. 24. 10:48

순환 의존성 문제 (Circular Dependency Issues)

순환 의존성

  • shared_ptr가 클래스 내부에서 계속 존재하게 되면, 인스턴스가 소멸자를 호출하지 못한다.

  • 만약 인스턴스 두 개가 멤버로 서로를 shared_ptr로 가지고 있다면, 마치 교착 상태처럼 소멸자를 둘 다 호출하지 못하는 상태가 된다.

    #include <iostream>
    #include <memory>
    
    class Person
    {
      std::string                name_;
      std::shared_ptr<Person>    partner_;
    
    public:
      Person(const std::string& name) : name_(name)
      {
        std::cout << name_ << " created\n";
      }
    
      ~Person()
      {
        std::cout << name_ << " destroyed\n";
      }
    
      friend bool partnerUp(std::shared_ptr<Person>& p1, std::shared_ptr<Person>& p2)
      {
        if (!p1 || !p2)
          return false;
    
        p1->partner_ = p2;
        p2->partner_ = p1;
    
        std::cout << p1->name_ << " is partnered with " << p2->name_ << '\n';
    
        return true;
      }
    
      const std::string& getName() const
      {
        return name_;
      }
    };
    
    int            main()
    {
      auto lucy = std::make_shared<Person>("Lucy");
      auto ricky = std::make_shared<Person>("Ricky");
    
      partnerUp(lucy, ricky);
    }
    
    /* stdout stderr
    Lucy created
    Ricky created
    Lucy is partnered with Ricky
    */
    • 소멸자가 호출되지 않는 것을 볼 수 있다.

    • 마지막 줄의 partnerUp(lucy, ricky);를 지우면 정상적으로 소멸자가 호출된다.

    • weak_ptr로 해결할 수 있다.

C++/Library 2021. 3. 24. 10:48

weak_ptr

weak_ptr

  • <memory> 라이브러리

  • shared_ptr의 순환 의존성 문제를 해결할 수 있다.

    #include <iostream>
    #include <memory>
    
    class Person
    {
      std::string            name_;
      std::weak_ptr<Person>    partner_;
    
    public:
      Person(const std::string& name) : name_(name)
      {
        std::cout << name_ << " created\n";
      }
    
      ~Person()
      {
        std::cout << name_ << " destroyed\n";
      }
    
      friend bool partnerUp(std::shared_ptr<Person>& p1, std::shared_ptr<Person>& p2)
      {
        if (!p1 || !p2)
          return false;
    
        p1->partner_ = p2;
        p2->partner_ = p1;
    
        std::cout << p1->name_ << " is partnered with " << p2->name_ << '\n';
    
        return true;
      }
    
      const std::string& getName() const
      {
        return name_;
      }
    };
    
    int            main()
    {
      auto lucy = std::make_shared<Person>("Lucy");
      auto ricky = std::make_shared<Person>("Ricky");
    
      partnerUp(lucy, ricky);
    }
    
    /* stdout stderr
    Lucy created
    Ricky created
    Lucy is partnered with Ricky
    Ricky destroyed
    Lucy destroyed
    */
  • mutex처럼 lock이 걸린 상태에서만 개수를 세기 때문에, 인스턴스를 사용 중이 아니면 소멸자를 호출할 수 있는 원리이다.

    • lock 함수를 사용하면 shared_ptr를 반환한다.

      #include <iostream>
      #include <memory>
      
      class Person
      {
        std::string                name_;
        std::weak_ptr<Person>    partner_;
      
      public:
        Person(const std::string& name) : name_(name)
        {
          std::cout << name_ << " created\n";
        }
      
        ~Person()
        {
          std::cout << name_ << " destroyed\n";
        }
      
        friend bool partnerUp(std::shared_ptr<Person>& p1, std::shared_ptr<Person>& p2)
        {
          if (!p1 || !p2)
            return false;
      
          p1->partner_ = p2;
          p2->partner_ = p1;
      
          std::cout << p1->name_ << " is partnered with " << p2->name_ << '\n';
      
          return true;
        }
      
        const std::string& getName() const
        {
          return name_;
        }
      
        const std::shared_ptr<Person> getPartner() const
        {
          return partner_.lock();
        }
      };
      
      int            main()
      {
        auto lucy = std::make_shared<Person>("Lucy");
        auto ricky = std::make_shared<Person>("Ricky");
      
        partnerUp(lucy, ricky);
      
        std::cout << lucy->getName() << std::endl;
        std::cout << lucy->getPartner()->getName() << std::endl;
      }
      
      /* stdout stderr
      Lucy created
      Ricky created
      Lucy is partnered with Ricky
      Lucy
      Ricky
      Ricky destroyed
      Lucy destroyed
      */

'C++ > Library' 카테고리의 다른 글

C++ STL 컨테이너 (STL Containers)  (0) 2021.03.24
C++ 표준 템플릿 라이브러리 (STL, Standard Template Libraries)  (0) 2021.03.24
C++ shared_ptr  (0) 2021.03.24
C++ unique_ptr  (0) 2021.03.24
C++ std::move  (0) 2021.03.24
C++/Library 2021. 3. 24. 10:47

shared_ptr

shared_ptr

  • <memory> 라이브러리

  • 몇 개의 변수가 포인터를 참조하고 있는지 내부적으로 계산한다.


예제

main.cpp

  #include <iostream>
  #include <memory>
  #include "Resource.h"

  int            main()
  {
    Resource* res = new Resource(3);
    res->setAll(1);

    {
      std::shared_ptr<Resource> ptr1(res);

      ptr1->print();

      {
        std::shared_ptr<Resource> ptr2(ptr1);

        ptr2->setAll(3);
        ptr2->print();

        std::cout << "Going out of the block\n";
      }

      ptr1->print();
      std::cout << "Going out of the outer block\n";
    }
    std::cout << "Last of main function\n";
  }

  /* stdout stderr
  Resource length constructed
  1 1 1
  3 3 3
  Going out of the block
  3 3 3
  Going out of the outer block
  Resource destoryed
  Last of main function
  */

Resource.h

  #pragma once

  #include <iostream>

  class Resource
  {
  public:
    int    *data_ = nullptr;
    unsigned length_ = 0;

    Resource()
    {
      std::cout << "Resource default constructed\n";
    }

    Resource(unsigned length)
    {
      std::cout << "Resource length constructed\n";
      init(length);
    }

    Resource(const Resource& res)
    {
      std::cout << "Resource copy constructed\n";
      init(res.length_);
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = res.data_[i];
    }

    ~Resource()
    {
      std::cout << "Resource destoryed\n";

      if (data_ != nullptr) delete[] data_;
    }

    void    init(unsigned length)
    {
      data_ = new int[length];
      length_ = length;
    }

    Resource& operator = (Resource& res)
    {
      std::cout << "Resource copy assignment\n";
      if (&res == this) return *this;

      if (data_ != nullptr) delete[] data_;
      init(res.length_);
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = res.data_[i];
      return *this;
    }

    void    print()
    {
      for (unsigned i = 0; i < length_; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }

    void    setAll(const int& v)
    {
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = v;
    }
  };
  • 아래와 같이 ptr2를 생성하면 ptr1에서 알 수 없기 때문에 에러가 발생한다.

    std::shared_ptr<Resource> ptr2(res);
  • make_shared를 사용해서 직접 초기화하는 방법이 일반적이다.

    main.cpp

    #include <iostream>
    #include <memory>
    #include "Resource.h"
    
    int            main()
    {
      {
        auto ptr1 = std::make_shared<Resource>(3);
        ptr1->setAll(1);
        ptr1->print();
    
        {
          auto ptr2 = ptr1;
    
          ptr2->setAll(3);
          ptr2->print();
    
          std::cout << "Going out of the block\n";
        }
    
        ptr1->print();
        std::cout << "Going out of the outer block\n";
      }
      std::cout << "Last of main function\n";
    }
    
    /* stdout stderr
    Resource length constructed
    1 1 1
    3 3 3
    Going out of the block
    3 3 3
    Going out of the outer block
    Resource destoryed
    Last of main function
    */

'C++ > Library' 카테고리의 다른 글

C++ 표준 템플릿 라이브러리 (STL, Standard Template Libraries)  (0) 2021.03.24
C++ weak_ptr  (0) 2021.03.24
C++ unique_ptr  (0) 2021.03.24
C++ std::move  (0) 2021.03.24
C++ 출력 스트림 끊기  (0) 2021.03.24
C++/Library 2021. 3. 24. 10:47

unique_ptr

unique_ptr

  • <memory> 라이브러리

  • scope를 벗어나면 자동으로 메모리를 해제하므로 메모리 누수를 방지할 수 있다.


예제

main.cpp

  #include <iostream>
  #include <memory>
  #include "Resource.h"

  using namespace std;

  int            main()
  {
    std::unique_ptr<Resource> res(new Resource(100000000));
  }
  /* stdout
  Resource length constructed
  Resource destoryed
  */

Resource.h

  #pragma once

  #include <iostream>

  class Resource
  {
  public:
    int    *data_ = nullptr;
    unsigned length_ = 0;

    Resource()
    {
      std::cout << "Resource default constructed\n";
    }

    Resource(unsigned length)
    {
      std::cout << "Resource length constructed\n";
      init(length);
    }

    Resource(const Resource& res)
    {
      std::cout << "Resource copy constructed\n";
      init(res.length_);
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = res.data_[i];
    }

    ~Resource()
    {
      std::cout << "Resource destoryed\n";

      if (data_ != nullptr) delete[] data_;
    }

    void    init(unsigned length)
    {
      data_ = new int[length];
      length_ = length;
    }

    Resource& operator = (Resource& res)
    {
      std::cout << "Resource copy assignment\n";
      if (&res == this) return *this;

      if (data_ != nullptr) delete[] data_;
      init(res.length_);
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = res.data_[i];
      return *this;
    }

    void    print()
    {
      for (unsigned i = 0; i < length_; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }

    void    setAll(const int& v)
    {
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = v;
    }
  };

make_unique

  • unique_ptr를 안전하게 반환하는 함수

    main.cpp

    #include <iostream>
    #include <memory>
    #include "Resource.h"
    
    using namespace std;
    
    auto        doSomething()
    {
      //return std::unique_ptr<Resource>(new Resource(5));
      return std::make_unique<Resource>(5); // 이걸 더 추천
    }
    
    int            main()
    {
      auto res1 = doSomething();
    
      res1->setAll(5);
    
      std::unique_ptr<Resource> res2;
    
      if (res1 != nullptr)
      {
        cout << "res1 : ";
        res1->print();
      }
      if (res2 != nullptr)
      {
        cout << "res2 : ";
        res2->print();
      }
    
      cout << boolalpha;
      cout << "res1 : " << static_cast<bool>(res1) << endl;
      cout << "res2 : " << static_cast<bool>(res2) << endl;
      cout << '\n';
    
      res2 = std::move(res1);
    
      if (res1 != nullptr) 
      {
        cout << "res1 : ";
        res1->print();
      }
      if (res2 != nullptr) 
      {
        cout << "res2 : ";
        res2->print();
      }
    
      cout << "res1 : " << static_cast<bool>(res1) << endl;
      cout << "res2 : " << static_cast<bool>(res2) << endl;
    }
    
    /* stdout stderr
    Resource length constructed
    res1 : 5 5 5 5 5
    res1 : true
    res2 : false
    
    res2 : 5 5 5 5 5
    res1 : false
    res2 : true
    Resource destoryed
    */
    • 아래의 명령은 에러가 발생한다.

      main.cpp

      res2 = res1;
      error C2280: 'std::unique_ptr<Resource,std::default_delete<Resource>> &std::unique_ptr<Resource,std::default_delete<Resource>>::operator =(const std::unique_ptr<Resource,std::default_delete<Resource>> &)': attempting to reference a deleted function
      • unique_ptr는 말 그대로 포인터를 단독 사용하기 위해 쓰는 클래스이므로, 라이브러리 자체에서 복사 대입(copy assignment, operator =)을 delete한 것이다.

copy constructordelete한 상태임을 보여주는 예제

main.cpp

  #include <iostream>
  #include <memory>
  #include "Resource.h"

  using namespace std;

  auto        doSomething(std::unique_ptr<Resource> res)  // 에러
  {
    res->setAll(10);
  }

  int            main()
  {
    auto res1 = std::make_unique<Resource>(5);
    res1->setAll(1);
    res1->print();

    doSomething(res1);

    res1->print();
  }
  error C2280: 'std::unique_ptr<Resource,std::default_delete<Resource>>::unique_ptr(const std::unique_ptr<Resource,std::default_delete<Resource>> &)': attempting to reference a deleted function
  • 에러 메시지를 통해 라이브러리에서 복사 생성자(copy constructor)를 delete한 것임을 알 수 있다.
  • 이 예제에서 doSomething(std::move(res1)) 과 같이 R-value로 넘기면, doSomething 함수 내부로 unique_ptrres1이 옮겨간다.

    • 해당 함수의 scope 밖으로 나오는 순간(함수가 끝나고 스택이 되감길 때) unique_ptr의 특성으로 res가 사라진다.

    • 글로 설명하는 것보다 예제를 보는게 쉽다.

    main.cpp

    #include <iostream>
    #include <memory>
    #include "Resource.h"
    
    using namespace std;
    
    auto        doSomething(std::unique_ptr<Resource> res)
    {
      res->setAll(10);
    }
    
    int            main()
    {
      auto res1 = std::make_unique<Resource>(5);
      res1->setAll(1);
      res1->print();
    
      cout << boolalpha << static_cast<bool>(res1) << endl;
    
      doSomething(std::move(res1));
    
      cout << static_cast<bool>(res1) << endl;
    }
    
    /* stdout stderr
    Resource length constructed
    1 1 1 1 1
    true
    Resource destoryed
    false
    */

get()

  • unique_ptr로 가지고 있는 포인터를 반환한다.

<memory>

// STRUCT TEMPLATE _Get_deleter_pointer_type
template <class _Ty, class _Dx_noref, class = void>
struct _Get_deleter_pointer_type { // provide fallback
    using type = _Ty*;
};

...

template <class _Ty, class _Dx /* = default_delete<_Ty> */>
class unique_ptr {
public:
  using pointer      = typename _Get_deleter_pointer_type<_Ty, remove_reference_t<_Dx>>::type;

...

_NODISCARD pointer get() const noexcept {
    return _Mypair._Myval2;
}

...

private:
  template <class, class>
  friend class unique_ptr;

  _Compressed_pair<_Dx, pointer> _Mypair;
};

<xmemory>

template <class _Ty1, class _Ty2, bool = is_empty_v<_Ty1> && !is_final_v<_Ty1>>
class _Compressed_pair final : private _Ty1 { // store a pair of values, deriving from empty first
public:
    _Ty2 _Myval2;

...
};

'C++ > Library' 카테고리의 다른 글

C++ weak_ptr  (0) 2021.03.24
C++ shared_ptr  (0) 2021.03.24
C++ std::move  (0) 2021.03.24
C++ 출력 스트림 끊기  (0) 2021.03.24
C++ std::exception  (0) 2021.03.22
C++/Library 2021. 3. 24. 10:45

std::move

  • <utility> 라이브러리

  • 인자로 들어온 값을 R-value로 리턴해준다.


예제

  • std::move를 사용하지 않은 예제

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    using namespace std;
    
    int            main()
    {
      AutoPtr<Resource> res1(new Resource(10000000));
    
      cout << res1.ptr_ << endl;
    
      AutoPtr<Resource> res2 = res1;
    
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    }
    
    /* stdout stderr
    Resource length constructed
    AutoPtr default constructor
    0158E360
    AutoPtr copy constructor
    Resource default constructed
    Resource copy assignment
    0158E360
    0158E408
    AutoPtr destructor
    Resource destoryed
    AutoPtr destructor
    Resource destoryed
    */

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    *data_ = nullptr;
      unsigned length_ = 0;
    
      Resource()
      {
        std::cout << "Resource default constructed\n";
      }
    
      Resource(unsigned length)
      {
        std::cout << "Resource length constructed\n";
        init(length);
      }
    
      Resource(const Resource& res)
      {
        std::cout << "Resource copy constructed\n";
        init(res.length_);
        for (unsigned i = 0; i < length_; ++i)
          data_[i] = res.data_[i];
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
    
        if (data_ != nullptr) delete[] data_;
      }
    
      void    init(unsigned length)
      {
        data_ = new int[length];
        length_ = length;
      }
    
      Resource& operator = (Resource& res)
      {
        std::cout << "Resource copy assignment\n";
        if (&res == this) return *this;
    
        if (data_ != nullptr) delete[] data_;
        init(res.length_);
        for (unsigned i = 0; i < length_; ++i)
          data_[i] = res.data_[i];
        return *this;
      }
    
      void print()
      {
        for (unsigned i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {
        std::cout << "AutoPtr default constructor\n";
      }
    
      AutoPtr(const AutoPtr& a)
        : ptr_(a.ptr_)
      {
        std::cout << "AutoPtr copy constructor\n";
        ptr_ = new T;
        *ptr_ = *a.ptr_;
      }
    
      AutoPtr(AutoPtr&& a)
        : ptr_(a.ptr_)
      {
        std::cout << "AutoPtr move constructor\n";
        a.ptr_ = nullptr;
      }
    
      ~AutoPtr()
      {
        std::cout << "AutoPtr destructor\n";
        if (ptr_ != nullptr) delete ptr_;
      }
    
      AutoPtr& operator = (const AutoPtr& a)
      {
        std::cout << "AutoPtr copy assignment\n";
        if (&a == this)
          return *this;
    
        if (ptr_ != nullptr) delete ptr_;
    
        ptr_ = new T;
        *ptr_ = *a.ptr_;
        return *this; 
      }
    
      AutoPtr& operator = (AutoPtr&& a)
      {
        std::cout << "AutoPtr move assignment\n";
    
        if (&a == this)
          return *this;
    
        if (ptr_ != nullptr) delete ptr_;
    
        ptr_ = a.ptr_;
        a.ptr_ = nullptr;
    
        return *this;
      }
    };

  • 위의 코드에서 main.cpp<utility> 라이브러리를 include 하고, std::move를 사용하면 다음과 같다.

    main.cpp

    #include <iostream>
    #include <utility>
    #include "Resource.h"
    #include "AutoPtr.h"
    using namespace std;
    
    int            main()
    {
      AutoPtr<Resource> res1(new Resource(10000000));
    
      cout << res1.ptr_ << endl;
    
      AutoPtr<Resource> res2 = std::move(res1);
    
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    }
    
    /* stdout stderr
    Resource length constructed
    AutoPtr default constructor
    00CADF50
    AutoPtr move constructor
    00000000
    00CADF50
    AutoPtr destructor
    Resource destoryed
    AutoPtr destructor
    */

Swap 예제

main.cpp

  #include <iostream>
  #include <utility>
  #include "Resource.h"
  #include "AutoPtr.h"
  using namespace std;

  template<typename T>
  void        MySwap(T& a, T& b)
  {
    T tmp{ std::move(a) };
    a = std::move(b);
    b = std::move(tmp);
  }

  int            main()
  {
    AutoPtr<Resource> res1(new Resource(3));
    res1->setAll(3);

    AutoPtr<Resource> res2(new Resource(5));
    res2->setAll(5);

    res1->print();
    res2->print();

    MySwap(res1, res2);

    res1->print();
    res2->print();
  }

  /* stdout stderr
  Resource length constructed
  AutoPtr default constructor
  Resource length constructed
  AutoPtr default constructor
  3 3 3
  5 5 5 5 5
  AutoPtr move constructor
  AutoPtr move assignment
  AutoPtr move assignment
  AutoPtr destructor
  5 5 5 5 5
  3 3 3
  AutoPtr destructor
  Resource destoryed
  AutoPtr destructor
  Resource destoryed
  */

Resource.h

  #pragma once

  #include <iostream>

  class Resource
  {
  public:
    int    *data_ = nullptr;
    unsigned length_ = 0;

    Resource()
    {
      std::cout << "Resource default constructed\n";
    }

    Resource(unsigned length)
    {
      std::cout << "Resource length constructed\n";
      init(length);
    }

    Resource(const Resource& res)
    {
      std::cout << "Resource copy constructed\n";
      init(res.length_);
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = res.data_[i];
    }

    ~Resource()
    {
      std::cout << "Resource destoryed\n";

      if (data_ != nullptr) delete[] data_;
    }

    void    init(unsigned length)
    {
      data_ = new int[length];
      length_ = length;
    }

    Resource& operator = (Resource& res)
    {
      std::cout << "Resource copy assignment\n";
      if (&res == this) return *this;

      if (data_ != nullptr) delete[] data_;
      init(res.length_);
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = res.data_[i];
      return *this;
    }

    void    print()
    {
      for (unsigned i = 0; i < length_; ++i)
        std::cout << data_[i] << ' ';
      std::cout << std::endl;
    }

    void    setAll(const int& v)
    {
      for (unsigned i = 0; i < length_; ++i)
        data_[i] = v;
    }
  };

AutoPtr.h

  #pragma once

  #include <iostream>

  template<class T>
  class AutoPtr
  {
  public:
    T* ptr_;

    AutoPtr(T *ptr = nullptr)
      : ptr_(ptr)
    {
      std::cout << "AutoPtr default constructor\n";
    }

    AutoPtr(const AutoPtr& a)
      : ptr_(a.ptr_)
    {
      std::cout << "AutoPtr copy constructor\n";
      ptr_ = new T;
      *ptr_ = *a.ptr_;
    }

    AutoPtr(AutoPtr&& a)
      : ptr_(a.ptr_)
    {
      std::cout << "AutoPtr move constructor\n";
      a.ptr_ = nullptr;
    }

    ~AutoPtr()
    {
      std::cout << "AutoPtr destructor\n";
      if (ptr_ != nullptr) delete ptr_;
    }

    AutoPtr& operator = (const AutoPtr& a)
    {
      std::cout << "AutoPtr copy assignment\n";
      if (&a == this)
        return *this;

      if (ptr_ != nullptr) delete ptr_;

      ptr_ = new T;
      *ptr_ = *a.ptr_;
      return *this; 
    }

    AutoPtr& operator = (AutoPtr&& a)
    {
      std::cout << "AutoPtr move assignment\n";

      if (&a == this)
        return *this;

      if (ptr_ != nullptr) delete ptr_;

      ptr_ = a.ptr_;
      a.ptr_ = nullptr;

      return *this;
    }

      T* operator->() const { return ptr_; }
  };

대부분의 라이브러리는 R-value에 대해서도 정의가 되어있다.

  #include <iostream>
  #include <utility>
  #include <vector>
  using namespace std;

  int            main()
  {
    vector<string> v;
    string str = "Hello";

    v.push_back(str);

    cout << str << endl;
    cout << v[0] << endl;

    v.push_back(std::move(str));

    cout << str << endl;
    cout << v[0] << ' ' << v[1] << endl;
  }

  /* stdout stderr
  Hello
  Hello

  Hello Hello
  */

'C++ > Library' 카테고리의 다른 글

C++ shared_ptr  (0) 2021.03.24
C++ unique_ptr  (0) 2021.03.24
C++ 출력 스트림 끊기  (0) 2021.03.24
C++ std::exception  (0) 2021.03.22
C++ reference_wrapper  (0) 2021.03.21
C++/SmartPointer 2021. 3. 24. 10:44

이동 생성자와 이동 대입 (Move Constructor and Move Assignment)

속도 비교

  • L-value 레퍼런스와 R-value 레퍼런스의 성능 차이가 꽤 존재한다.

    • R-value 레퍼런스의 경우 Deep Copy를 하지 않기 때문이다.

    • 생성자, 소멸자를 호출하는 부분이 강의와 살짝 달랐는데, 강의는 Release 모드여서 Debug 모드에 비해 단계가 줄어있는 상태여서 그랬다.

      • 나도 x86버전의 Release모드로 컴파일했더니 실행 파일이 없다는 LNK1104 에러가 발생하였다.

      • 백신 문제였는데, x64로 컴파일하니 실행에 문제는 없었다. 이유는 모르겠다.

      • 아래는 Debug 모드로 실행했다.

  • L-value 레퍼런스를 사용한 예제

    • Copy Constructor, Copy Assignment를 이용해 값을 Deep Copy한다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    #include "Timer.h"
    
    using namespace std;
    
    AutoPtr<Resource> generateResource()
    {
      AutoPtr<Resource> res(new Resource(10000000));
    
      return res;
    }
    
    int            main()
    {
      Timer timer;
      {
        AutoPtr<Resource> main_res;
        main_res = generateResource();
      }
      timer.elapsed();
    }
    
    /* stdout stderr
    AutoPtr default constructor
    Resource length constructed
    AutoPtr default constructor
    AutoPtr copy constructor
    Resource default constructed
    Resource copy assignment
    AutoPtr destructor
    Resource destoryed
    AutoPtr copy assignment
    Resource default constructed
    Resource copy assignment
    AutoPtr destructor
    Resource destoryed
    AutoPtr destructor
    Resource destoryed
    0.0771911
    */

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    *data_ = nullptr;
      unsigned length_ = 0;
    
      Resource()
      {
        std::cout << "Resource default constructed\n";
      }
    
      Resource(unsigned length)
      {
        std::cout << "Resource length constructed\n";
        init(length);
      }
    
      Resource(const Resource& res)
      {
        std::cout << "Resource copy constructed\n";
        init(res.length_);
        for (unsigned i = 0; i < length_; ++i)
          data_[i] = res.data_[i];
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
    
        if (data_ != nullptr) delete[] data_;
      }
    
      void    init(unsigned length)
      {
        data_ = new int[length];
        length_ = length;
      }
    
      Resource& operator = (Resource& res)
      {
        std::cout << "Resource copy assignment\n";
        if (&res == this) return *this;
    
        if (data_ != nullptr) delete[] data_;
        init(res.length_);
        for (unsigned i = 0; i < length_; ++i)
          data_[i] = res.data_[i];
        return *this;
      }
    
      void print()
      {
        for (unsigned i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {
        std::cout << "AutoPtr default constructor\n";
      }
    
      AutoPtr(const AutoPtr& a)
      {
        std::cout << "AutoPtr copy constructor\n";
        ptr_ = new T;
        *ptr_ = *a.ptr_;
      }
    
      ~AutoPtr()
      {
        std::cout << "AutoPtr destructor\n";
        if (ptr_ != nullptr) delete ptr_;
      }
    
      AutoPtr& operator = (const AutoPtr& a)
      {
        std::cout << "AutoPtr copy assignment\n";
        if (&a == this)
          return *this;
    
        if (ptr_ != nullptr) delete ptr_;
    
        ptr_ = new T;
        *ptr_ = *a.ptr_;
        return *this;
      }
    };

    Timer.h

    #pragma once
    
    #include <iostream>
    #include <chrono>
    
    class Timer
    {
        using clock_t = std::chrono::high_resolution_clock;
        using second_t = std::chrono::duration<double, std::ratio<1>>;
    
        std::chrono::time_point<clock_t> start_time = clock_t::now();
    
    public:
        void elapsed()
        {
            std::chrono::time_point<clock_t> end_time = clock_t::now();
    
            std::cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << std::endl;
        }
    };

  • R-value 레퍼런스를 사용한 예제

    • AutoPtr 클래스에서 Move Constructor, Move Assignment를 이용했다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    #include "Timer.h"
    
    using namespace std;
    
    AutoPtr<Resource> generateResource()
    {
      AutoPtr<Resource> res(new Resource(10000000));
    
      return res;
    }
    
    int            main()
    {
      Timer timer;
      {
        AutoPtr<Resource> main_res;
        main_res = generateResource();
      }
    
      timer.elapsed();
    }
    
    /* stdout stderr
    AutoPtr default constructor
    Resource length constructed
    AutoPtr default constructor
    AutoPtr move constructor
    AutoPtr destructor
    AutoPtr move assignment
    AutoPtr destructor
    AutoPtr destructor
    Resource destoryed
    0.0090543
    */

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    *data_ = nullptr;
      unsigned length_ = 0;
    
      Resource()
      {
        std::cout << "Resource default constructed\n";
      }
    
      Resource(unsigned length)
      {
        std::cout << "Resource length constructed\n";
        init(length);
      }
    
      Resource(const Resource& res)
      {
        std::cout << "Resource copy constructed\n";
        init(res.length_);
        for (unsigned i = 0; i < length_; ++i)
          data_[i] = res.data_[i];
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
    
        if (data_ != nullptr) delete[] data_;
      }
    
      void    init(unsigned length)
      {
        data_ = new int[length];
        length_ = length;
      }
    
      Resource& operator = (Resource& res)
      {
        std::cout << "Resource copy assignment\n";
        if (&res == this) return *this;
    
        if (data_ != nullptr) delete[] data_;
        init(res.length_);
        for (unsigned i = 0; i < length_; ++i)
          data_[i] = res.data_[i];
        return *this;
      }
    
      void print()
      {
        for (unsigned i = 0; i < length_; ++i)
          std::cout << data_[i] << ' ';
        std::cout << std::endl;
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {
        std::cout << "AutoPtr default constructor\n";
      }
    
      AutoPtr(AutoPtr&& a)
        : ptr_(a.ptr_)
      {
        std::cout << "AutoPtr move constructor\n";
        a.ptr_ = nullptr;
      }
    
      ~AutoPtr()
      {
        std::cout << "AutoPtr destructor\n";
        if (ptr_ != nullptr) delete ptr_;
      }
    
      AutoPtr& operator = (AutoPtr&& a)
      {
        std::cout << "AutoPtr move assignment\n";
        if (&a == this)
          return *this;
    
        if (ptr_ != nullptr) delete ptr_;
    
        ptr_ = a.ptr_;
        a.ptr_ = nullptr;
        return *this;
      }
    };

    Timer.h

    #pragma once
    
    #include <iostream>
    #include <chrono>
    
    class Timer
    {
        using clock_t = std::chrono::high_resolution_clock;
        using second_t = std::chrono::duration<double, std::ratio<1>>;
    
        std::chrono::time_point<clock_t> start_time = clock_t::now();
    
    public:
        void elapsed()
        {
            std::chrono::time_point<clock_t> end_time = clock_t::now();
    
            std::cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << std::endl;
        }
    };
  • 보다 정확한 시간을 측정하려면 출력 하지 않은 채로 시간을 재면 된다.

'C++ > SmartPointer' 카테고리의 다른 글

C++ 순환 의존성 문제 (Circular Dependency Issues)  (0) 2021.03.24
C++ R-value Reference  (0) 2021.03.24
C++ Syntax vs Semantics  (0) 2021.03.24
C++ 스마트 포인터 (Smart Pointer)  (0) 2021.03.22
C++/Library 2021. 3. 24. 10:43

출력 스트림 끊기

  • <iostream> 라이브러리 사용

  • std::cout.rdbuf(streambuf *)

    <iosfwd>

    using streambuf     = basic_streambuf<char, char_traits<char>>;

    <ios>

    using _Mysb       = basic_streambuf<_Elem, _Traits>;
    
    ...
    
    _Mysb* __CLR_OR_THIS_CALL rdbuf(_Mysb* _Strbuf) { // set stream buffer pointer
        _Mysb* _Oldstrbuf = _Mystrbuf;
        _Mystrbuf         = _Strbuf;
        clear();
        return _Oldstrbuf;
    }
    • 인자로 입력한 stream buffer pointer를 출력 스트림으로 지정한다.

    • 예제

      #include <iostream>
      
      using namespace std;
      
      int            main()
      {
        streambuf* orig_buf = cout.rdbuf();
        cout.rdbuf(NULL);
      
        cout << "Hello ";
      
        cout.rdbuf(orig_buf);
      
        cout << "World\n";
      }
      
      /* stdout
      World
      */

'C++ > Library' 카테고리의 다른 글

C++ unique_ptr  (0) 2021.03.24
C++ std::move  (0) 2021.03.24
C++ std::exception  (0) 2021.03.22
C++ reference_wrapper  (0) 2021.03.21
C++ IntArray  (0) 2021.03.19
C++/SmartPointer 2021. 3. 24. 10:35

R-value Reference

R-value

  • L-value와 달리 메모리 주소가 저장되지 않는 값을 의미한다.

예제

  • 주소를 가지고 있지 않은 리터럴 값이나 함수의 반환 값 등을 참조할 수 있다.

    #include <iostream>
    
    using namespace std;
    
    void        doSomething(int& ref)
    {
      cout << "L-value ref\n";
    }
    
    void        doSomething(int&& ref)
    {
      cout << "R-value ref\n";
    }
    
    int            getResult()
    {
      return 100 * 100;
    }
    
    int            main()
    {
      int x = 5;
      int y = getResult();
      const int cx = 6;
      const int cy = getResult();
    
      // L-value References
    
      int& lr1 = x;
      //int& lr2 = cx;
      //int& lr3 = 5;
    
      const int& lr4 = x;
      const int& lr5 = cx;
      const int& lr6 = 5;
    
    
// R-value references

//int&& rr1 = x;
//int&& rr2 = cx;
int&& rr3 = 5;
int&& rrr = getResult();

cout << rr3 << endl;
rr3 = 10;
cout << rr3 << endl;

//const int&& rr4 = x;
//const int&& rr5 = cx;
const int&& rr6 = 5;

doSomething(x);
doSomething(5);
doSomething(getResult());

}

/* stdout stderr
5
10
L-value ref
R-value ref
R-value ref
*/
```

C++/SmartPointer 2021. 3. 24. 10:31

Syntax vs Semantics

  • 아래의 코드는 문법(syntax) 상 문제가 없으나, 정수 + 정수문자열 + 문자열의 의미(semantics)가 다르다.

      int x = 1, y = 1;
      x + y;
    
      std::string str1("Hello"), str2(" World");
      str1 + str2;

Syntax

  • 문법에 잘 맞아서 컴파일이 되는지

Semantics

  • 의미가 무엇인지

  • Value Semantics (Copy Semantics)

  • Reference Semantics

  • Move Semantics

C++/SmartPointer 2021. 3. 22. 00:15

스마트 포인터 (Smart Pointer)

RAII (Resource Acquisition Is Initialization)

  • C++의 창시자인 스트롭스트룹이 제안한 디자인 패턴이다.

    • C++ RAII(Resource Acquisition Is Initialization)의 해석을 요약하면 다음과 같다.

      • 객체의 유효성에 대해 다음의 관점이 필요하다.

        • 생성이 완료된 객체는 유효한 객체이어야 한다. 즉, 유효하지 않은 객체가 생성되어서는 안 된다.

        • 필요한 자원들을 획득하지 못한 객체는 유효한 객체가 아니다.

      • 대칭성에 의해, 소멸되어서 더 이상 유효하지 않게 된 객체는 어떤 자원도 가지고 있지 않아야 한다.

  • 메모리 누수를 방지하는 기법이다.

    • 동적으로 할당한 메모리, 혹은 파일을 open한 뒤 early return 혹은 throw 등 예기치 못하게 스택을 되감는 경우에 메모리 누수가 발생할 수 있다.

      main.cpp

      #include <iostream>
      #include "Resource.h"
      
      using namespace std;
      
      void        doSomething()
      {
        Resource* res = new Resource;
      
        if (true)
          return ;
      
        delete res;
      }
      
      int            main()
      {
        doSomething();
      }
      
      /* stdout stderr
      Resource constructed
      */

      Resource.h

      #pragma once
      
      #include <iostream>
      
      class Resource
      {
      public:
        int    data_[100];
      
        Resource()
        {
          std::cout << "Resource constructed\n";
        }
      
        ~Resource()
        {
          std::cout << "Resource destoryed\n";
        }
      };

auto_ptr

  • C++98부터 auto_ptr 클래스가 존재했는데, 앞으로는 사용하지 않아야 되는 클래스이다.

    • 예기치 못한 상황이 발생할 수 있기 때문에, 더 안정적인 unique_ptr 등을 사용하면 된다.
  • 스택에 할당된 인스턴스가 자동으로 소멸되는 것을 이용했다.

  • 아래는 auto_ptr를 비슷하게 구현해보는 예제이다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething()
    {
      AutoPtr<Resource> res(new Resource);
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Resource constructed
    Resource destoryed
    */

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    data_[100];
    
      Resource()
      {
        std::cout << "Resource constructed\n";
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_ = nullptr;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {}
    
      ~AutoPtr()
      {
        if (ptr_ != nullptr) delete ptr_;
      }
    
      T& operator*() const { return *ptr_; }
      T* operator->() const { return ptr_; }
      bool isNull() const { return ptr_ == nullptr; }
    };

Move Semantics

  • 다음과 같은 경우 double free 문제가 발생하여 런타임 에러를 만날 수 있다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething()
    {
      AutoPtr<Resource> res1(new Resource);
      AutoPtr<Resource> res2;
    
      cout << boolalpha;
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    
      res2 = res1;
    
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    }  // 여기서 소멸자가 호출될 때 메모리를 두 번 해제하게 되어 에러 발생
    
    int            main()
    {
      doSomething();
    }

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    data_[100];
    
      Resource()
      {
        std::cout << "Resource constructed\n";
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_ = nullptr;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {}
    
      ~AutoPtr()
      {
        if (ptr_ != nullptr) delete ptr_;
      }
    
      T& operator*() const { return *ptr_; }
      T* operator->() const { return ptr_; }
      bool isNull() const { return ptr_ == nullptr; }
    };

  • 이런 상황을 방지하려면 copy construnctorassignment operator를 오버로딩해야 한다.

    • AutoPtr 클래스가 복사 생성자를 호출하거나 대입 연산을 할 때, 내부의 포인터가 옮겨가는 방식으로 구현한다.

    • 이렇게 되면 같은 포인터를 한 인스턴스만 갖도록 하여 메모리 해제의 중복 문제를 피할 수 있다.

    main.cpp

    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething()
    {
      AutoPtr<Resource> res1(new Resource);
      AutoPtr<Resource> res2;
    
      cout << boolalpha;
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    
      res2 = res1;  // move semantics
    
      cout << res1.ptr_ << endl;
      cout << res2.ptr_ << endl;
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Resource constructed
    00E8E518
    00000000
    00000000
    00E8E518
    Resource destoryed
    */

    Resource.h

    #pragma once
    
    #include <iostream>
    
    class Resource
    {
    public:
      int    data_[100];
    
      Resource()
      {
        std::cout << "Resource constructed\n";
      }
    
      ~Resource()
      {
        std::cout << "Resource destoryed\n";
      }
    };

    AutoPtr.h

    #pragma once
    
    #include <iostream>
    
    template<class T>
    class AutoPtr
    {
    public:
      T* ptr_ = nullptr;
    
      AutoPtr(T *ptr = nullptr)
        : ptr_(ptr)
      {}
    
      AutoPtr(T& a)
      {
        ptr_ = a.ptr_;
        a.ptr_ = nullptr;
      }
    
      ~AutoPtr()
      {
        if (ptr_ != nullptr) delete ptr_;
      }
    
      AutoPtr& operator = (AutoPtr& a)
      {
        if (&a == this)
          return *this;
    
        delete ptr_;
        ptr_ = a.ptr_;
        a.ptr_ = nullptr;
        return *this;
      }
    
      T& operator*() const { return *ptr_; }
      T* operator->() const { return ptr_; }
      bool isNull() const { return ptr_ == nullptr; }
    };

Syntax vs Semantics


AutoPtr의 한계

  • 다음과 같이 함수 안으로 move semantics가 적용되는 경우, 해당 함수가 끝나면서 메모리가 해제된다.

    • 이후 해당 인스턴스가 또 소멸되는 상황이므로 런타임 에러가 발생한다.
    #include <iostream>
    #include "Resource.h"
    #include "AutoPtr.h"
    
    using namespace std;
    
    void        doSomething2(AutoPtr<Resource> res)
    {
    
    }
    
    void        doSomething()
    {
      AutoPtr<Resource> res1(new Resource);
    
      doSomething2(res1);
    }  // 에러
    
    int            main()
    {
      doSomething();
    }
C++/TBC++ 2021. 3. 22. 00:14

따라하며 배우는 C++ 15장

스마트 포인터 (Smart Pointer)

R-value Reference

이동 생성자와 이동 대입

std::move

unique_ptr

shared_ptr

순환 의존성 문제 (Circular Dependency Issues)


'C++ > TBC++' 카테고리의 다른 글

따라하며 배우는 C++ 17장  (0) 2021.03.26
따라하며 배우는 C++ 16장  (0) 2021.03.24
C++ 따라하며 배우는 C++ 14장  (0) 2021.03.22
따라하며 배우는 C++ 13장  (0) 2021.03.21
따라하며 배우는 C++ 12장  (0) 2021.03.20
C++/Exception 2021. 3. 22. 00:13

예외 처리의 위험성과 단점

메모리 누수

  • throw 이후의 코드는 무시되기 때문에, 메모리 누수가 발생할 가능성이 존재한다.

    #include <iostream>
    
    using namespace std;
    
    int            main()
    {
      try
      {
        int* i = new int[100000];
    
        throw "error";
    
        delete[] i;
      }
      catch (...)
      {
        cout << "Catch\n";
      }
    }
    
    /* stdout stderr
    Catch
    */
    • unique_ptr를 사용하여 해결할 수 있다.

소멸자에서 throw 사용 금지

  • 소멸자에서는 throw의 사용이 금지되어있다.

    • 만약 사용하면 런타임 에러가 발생한다.
    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    class A
    {
    public:
      ~A()
      {
        throw "error";
      }
    };
    
    int            main()
    {
      try
      {
        A a;
      }
      catch (...)
      {
        cout << "Catch\n";
      }
    }
    warning C4297: 'A::~A': function assumed not to throw an exception but does
    message : destructor or deallocator has a (possibly implicit) non-throwing exception specification
C++/Exception 2021. 3. 22. 00:12

함수 try (Function try)

함수의 body 자체가 try, catch로 이루어진 경우

  • 함수를 정의할 때 중괄호로 감싸지 않아도 된다고 한다.

  • 잘 쓰이는 문법은 아닌 것 같다.

  • rethrow를 하지 않는다.

    #include <iostream>
    
    using namespace std;
    
    void        doSomething()
    try
    {
      throw - 1;
    }
    catch (...)
    {
      cout << "Catch in doSomething()\n";
    }
    
    int            main()
    {
      try
      {
        doSomething();
      }
      catch (...)
      {
        cout << "Catch in main()\n";
      }
    }
    
    /* stdout stderr
    Catch in doSomething()
    */

클래스의 생성자에서 예외가 발생할 경우

  • 생성자의 멤버 초기화 리스트까지 포함하여 try를 시도하고, 바로 아래에서 catch한다.

  • 자동으로 rethrow를 적용한다.

    #include <iostream>
    
    using namespace std;
    
    class A
    {
      int    x_;
    
    public:
      A(int x) : x_(x)
      {
        if (x <= 0)
          throw 1;
      }
    };
    
    class B : public A
    {
    public:
      B(int x) try : A(x)
      {}
      catch (...)
      {
        cout << "Catch in B constructor\n";
        //throw;
      }
    };
    
    int            main()
    {
      try
      {
        B b(0);
      }
      catch (...)
      {
        cout << "Catch in main()\n";
      }
    }
    
    /* stdout stderr
    Catch in B constructor
    Catch in main()
    */
C++/Library 2021. 3. 22. 00:11

std::exception

  • <exception> 라이브러리

exception 클래스

  • 자식으로 여러 클래스를 가지고 있다.

  • std::exception::what() 함수를 통해 예외 상황을 알 수 있다.

    • 다음과 같이 exception 클래스에서는 가상함수로 정의되어있다.

      _NODISCARD virtual char const* what() const
      {
          return _Data._What ? _Data._What : "Unknown exception";
      }
    • https://en.cppreference.com/w/cpp/error/length_error 에 상속 다이어그램이 있으니 참고하면 좋다.

    #include <iostream>
    #include <exception>
    
    using namespace std;
    
    int            main()
    {
      try
      {
        string    s;
        s.resize(-1);
      }
      catch (std::exception & exception)
      {
        cout << typeid(exception).name() << endl;
        cerr << exception.what() << endl;
      }
    }
    
    /* stdout stderr
    class std::length_error
    string too long
    */
  • 직접 자식 클래스를 던질 수도 있다.

    #include <iostream>
    #include <exception>
    
    using namespace std;
    
    int            main()
    {
      try
      {
        throw std::runtime_error("Bad thing happened");
      }
      catch (std::exception & exception)
      {
        cout << typeid(exception).name() << endl;
        cerr << exception.what() << endl;
      }
    }
    
    /* stdout stderr
    class std::runtime_error
    Bad thing happened
    */

직접 상속받는 클래스 만들기

  • std::exception 클래스를 상속받아 새로운 클래스를 정의하고 이를 사용할 수 있다.

    • what 함수를 오버라이딩해야 의미가 있다.

    • noexcept

      • C++11

      • 예외를 던지지 않는다는 뜻이다. 없어도 되는 것 같다.

    #include <iostream>
    #include <exception>
    
    using namespace std;
    
    class CustomException : public std::exception
    {
    public:
      const char* what() const noexcept override
      {
        return "Custom exception";
      }
    };
    
    int            main()
    {
      try
      {
        throw CustomException();
      }
      catch (std::exception & exception)
      {
        cout << typeid(exception).name() << endl;
        cerr << exception.what() << endl;
      }
    }
    
    /* stdout stderr
    class CustomException
    Custom exception
    */

'C++ > Library' 카테고리의 다른 글

C++ std::move  (0) 2021.03.24
C++ 출력 스트림 끊기  (0) 2021.03.24
C++ reference_wrapper  (0) 2021.03.21
C++ IntArray  (0) 2021.03.19
C++ initializer_list  (0) 2021.03.19
C++/Exception 2021. 3. 22. 00:10

예외 클래스와 상속 (Exception Class and Inheritance)

예외 클래스

  • 예외 상황을 처리할 때 필요한 변수나 함수를 멤버로 가진 클래스

  • 예외 클래스를 사용하지 않은 예제

    #include <iostream>
    
    using namespace std;
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw -1;
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (const int& x)
      {
        cerr << "Exception " << x << endl;
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception -1
    */

  • 예외 클래스를 직접 만들어서 사용하기

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw Exception();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception report
    */

예외 클래스를 상속해서 사용하기

  • 다형성을 적용하지 않는 경우

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class ArrayException : public Exception
    {
    public:
      void    report()
      {
        cerr << "Array exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw ArrayException();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception report
    */
    • 객체 잘림으로 인해 Exception::report() 함수가 작동한 것을 알 수 있다.
  • 다형성을 적용한 경우 (virtual 키워드 사용)

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      virtual void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class ArrayException : public Exception
    {
    public:
      void    report()
      {
        cerr << "Array exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw ArrayException();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Array exception report
    */

  • 맨 처음 catch된 부분만 처리된다.

    #include <iostream>
    
    using namespace std;
    
    class Exception
    {
    public:
      void    report()
      {
        cerr << "Exception report\n";
      }
    };
    
    class ArrayException : public Exception
    {
    public:
      void    report()
      {
        cerr << "Array exception report\n";
      }
    };
    
    class MyArray
    {
      int        data_[5];
    
    public:
      int& operator[] (const int& index)
      {
        if (index < 0 || index >= 5)
          throw ArrayException();
        return data_[index];
      }
    };
    
    void        doSomething()
    {
      MyArray    my_array;
    
      try
      {
        my_array[100];
      }
      catch (Exception& e)
      {
        e.report();
      }
      catch (ArrayException& e)
      {
        e.report();
      }
    }
    
    int            main()
    {
      doSomething();
    }
    
    /* stdout stderr
    Exception report
    */
    warning C4286: 'ArrayException &': is caught by base class ('Exception &') on line 44
    • 따로 처리해야 하는 경우에는 더 작은 범위의 클래스를 위에 두면 된다.

      #include <iostream>
      
      using namespace std;
      
      class Exception
      {
      public:
        void    report()
        {
          cerr << "Exception report\n";
        }
      };
      
      class ArrayException : public Exception
      {
      public:
        void    report()
        {
          cerr << "Array exception report\n";
        }
      };
      
      class MyArray
      {
        int        data_[5];
      
      public:
        int& operator[] (const int& index)
        {
          if (index < 0 || index >= 5)
            throw ArrayException();
          return data_[index];
        }
      };
      
      void        doSomething()
      {
        MyArray    my_array;
      
        try
        {
          my_array[100];
        }
        catch (ArrayException& e)
        {
          e.report();
        }
        catch (Exception& e)
        {
          e.report();
        }
      }
      
      int            main()
      {
        doSomething();
      }
      
      /* stdout stderr
      Array exception report
      */

rethrow

  • catch문 내부에서 throw를 한 번 더 사용할 경우, 값을 전달할 때 차이가 존재한다.

    • throw e; 형태로 직접 값을 넘겨줄 경우

      • 객체 잘림이 발생했을 경우 잘린 상태 그대로 넘어간다.
      #include <iostream>
      
      using namespace std;
      
      class Exception
      {
      public:
        void    report()
        {
          cerr << "Exception report\n";
        }
      };
      
      class ArrayException : public Exception
      {
      public:
        void    report()
        {
          cerr << "Array exception report\n";
        }
      };
      
      class MyArray
      {
        int        data_[5];
      
      public:
        int& operator[] (const int& index)
        {
          if (index < 0 || index >= 5)
            throw ArrayException();
          return data_[index];
        }
      };
      
      void        doSomething()
      {
        MyArray    my_array;
      
        try
        {
          my_array[100];
        }
        catch (Exception& e)
        {
          cout << "doSomething ";
          e.report();
          throw e;
        }
      }
      
      int            main()
      {
        try
        {
          doSomething();
        }
        catch (ArrayException& e)
        {
          cout << "Main ";
          e.report();
        }
        catch (Exception& e)
        {
          cout << "Main ";
          e.report();
        }
      }
      
      /* stdout stderr
      doSomething Exception report
      Main Exception report
      */
    • throw; 형태로 값 없이 키워드만 작성할 경우

      • catch한 값을 그대로 다시 throw해서 객체 잘림이 발생하지 않는다.
      #include <iostream>
      
      using namespace std;
      
      class Exception
      {
      public:
        void    report()
        {
          cerr << "Exception report\n";
        }
      };
      
      class ArrayException : public Exception
      {
      public:
        void    report()
        {
          cerr << "Array exception report\n";
        }
      };
      
      class MyArray
      {
        int        data_[5];
      
      public:
        int& operator[] (const int& index)
        {
          if (index < 0 || index >= 5)
            throw ArrayException();
          return data_[index];
        }
      };
      
      void        doSomething()
      {
        MyArray    my_array;
      
        try
        {
          my_array[100];
        }
        catch (Exception& e)
        {
          cout << "doSomething ";
          e.report();
          throw;
        }
      }
      
      int            main()
      {
        try
        {
          doSomething();
        }
        catch (ArrayException& e)
        {
          cout << "Main ";
          e.report();
        }
        catch (Exception& e)
        {
          cout << "Main ";
          e.report();
        }
      }
      
      /* stdout stderr
      doSomething Exception report
      Main Array exception report
      */

'C++ > Exception' 카테고리의 다른 글

C++ 예외 처리의 위험성과 단점  (0) 2021.03.22
C++ 함수 try (Function try)  (0) 2021.03.22
C++ 스택 되감기 (Stack Unwinding)  (0) 2021.03.22
C++ 예외 처리의 기본  (0) 2021.03.22
C++ 예외 처리 (Exception Handling)  (0) 2021.03.22
C++/Exception 2021. 3. 22. 00:10

스택 되감기 (Stack Unwinding)

  • throw를 통해 던질 경우, 타입에 맞는 catch를 만날 때까지 스택을 되감는다.

    • throw 뒤의 코드는 실행하지 않는다.

    • 마찬가지로 catch를 만나지 못해서 스택을 되감는 경우에도 뒤의 코드를 실행하지 않고 바로 리턴한다.

    • 아래 코드를 디버깅하여 한 줄씩 보면 이해하기 편하다.

    #include <iostream>
    
    using namespace std;
    
    void        last()
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw - 1;
    
      cout << "End Last\n";
    }
    
    void        third()
    {
      cout << "Third function\n";
    
      last();
    
      cout << "End Third\n";
    }
    
    void        second()
    {
      cout << "Second function\n";
    
      try
      {
        third();
      }
      catch (double)
      {
        cerr << "Second caught int exception\n";
      }
    
      cout << "End Second\n";
    }
    
    void        first()
    {
      cout << "First function\n";
    
      try
      {
        second();
      }
      catch (int)
      {
        cerr << "First caught int exception\n";
      }
    
      cout << "End First\n";
    }
    
    int            main()
    {
      cout << "Main Start\n";
    
      try
      {
        first();
      }
      catch (int)
      {
        cerr << "Main caught int exception\n";
      }
    
      cout << "End Main\n";
    }
    
    /* stdout stderr
    Main Start
    First function
    Second function
    Third function
    Last function
    Throw exception
    First caught int exception
    End First
    End Main
    */

Ellipses

  • 생략 부호(...)를 사용하여 명시하지 않은 예외들에 대한 처리를 할 수 있다.

    #include <iostream>
    
    using namespace std;
    
    void        last()
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw 'a';
    
      cout << "End Last\n";
    }
    
    void        third()
    {
      cout << "Third function\n";
    
      last();
    
      cout << "End Third\n";
    }
    
    void        second()
    {
      cout << "Second function\n";
    
      try
      {
        third();
      }
      catch (double)
      {
        cerr << "Second caught int exception\n";
      }
    
      cout << "End Second\n";
    }
    
    void        first()
    {
      cout << "First function\n";
    
      try
      {
        second();
      }
      catch (int)
      {
        cerr << "First caught int exception\n";
      }
    
      cout << "End First\n";
    }
    
    int            main()
    {
      cout << "Main Start\n";
    
      try
      {
        first();
      }
      catch (int)
      {
        cerr << "Main caught int exception\n";
      }
      catch (...)
      {
        cerr << "Main caught ellipses exception\n";
      }
    
      cout << "End Main\n";
    }
    
    /* stdout stderr
    Main Start
    First function
    Second function
    Third function
    Last function
    Throw exception
    Main caught ellipses exception
    End Main
    */

Exceiption Specifier

  • 함수를 정의할 때 body 전에 throw(자료형)의 형식으로 작성하여 예외를 던질 가능성이 있는 함수임을 명시할 수 있다.

    void        last() throw(...)
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw 'a';
    
      cout << "End Last\n";
    }
  • throw() 처럼 파라미터를 작성하지 않을 경우에는 "예외를 던지지 않는다" 라는 뜻으로 사용된다.

    void        last() throw()
    {
      cout << "Last function\n";
      cout << "Throw exception\n";
    
      throw 'a';
    
      cout << "End Last\n";
    }
    warning C4297: 'last': function assumed not to throw an exception but does
C++/Exception 2021. 3. 22. 00:10

예외 처리의 기본

try, throw, catch

  • try : 코드를 실행할 부분

  • throw : 예외 상황이 발생하면 해당 정보를 던지는 부분

  • catch : try 내부에서 throw가 발생했을 경우 이를 받아주는 부분

    #include <iostream>
    
    using namespace std;
    
    int            main()
    {
      double    x;
      cin >> x;
    
      try
      {
        if (x < 0.0)
          throw string("Negative input");
        cout << std::sqrt(x) << endl;
      }
      catch (string error_message)
      {
        cout << error_message << endl;
      }
    }
    
    /* stdin
    -10
    */
    
    /* stdout
    Negative input
    */

  • 묵시적 형변환이 허용되지 않는다.

    ex) std::string -> const char* 불가능

    #include <iostream>
    
    using namespace std;
    
    int            main()
    {
      double    x;
      cin >> x;
    
      try
      {
        if (x < 0.0)
          throw "Negative input";  // 런타임 에러
        cout << std::sqrt(x) << endl;
      }
      catch (string error_message)
      {
        cout << error_message << endl;
      }
    }

주의사항

  • 연산량이 많아서 처리속도가 느려지므로, 성능이 중요한 프로그램에서는 가급적 사용을 지양해야 한다.

    • 조건문으로 거를 수 있는 예외 상황은 조건문을 사용해서 처리하는게 좋다.

    • 예측 불가능한 예외 사항에 대해서만 사용하는게 좋다.

C++/Exception 2021. 3. 22. 00:09

예외 처리 (Exception Handling)

예외 처리의 기본

스택 되감기 (Stack Unwinding)

예외 클래스와 상속 (Exception Class and Inheritance)

std::exception

함수 try (Function try)

예외 처리의 위험성과 단점

C++/TBC++ 2021. 3. 22. 00:08

따라하며 배우는 C++ 14장

예외 처리 (Exception Handling)


'C++ > TBC++' 카테고리의 다른 글

따라하며 배우는 C++ 16장  (0) 2021.03.24
C++ 따라하며 배우는 C++ 15장  (0) 2021.03.22
따라하며 배우는 C++ 13장  (0) 2021.03.21
따라하며 배우는 C++ 12장  (0) 2021.03.20
따라하며 배우는 C++ 11장  (0) 2021.03.19