2018년 5월 21일 월요일

C++ 11/14 관련 내용 대충 정리, weak_ptr

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.


C++11/14/17 기능에 대한 지원(최신 C++)
https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


weak_ptr

https://msdn.microsoft.com/ko-kr/library/hh279672.aspx


- 참조수를 늘리지 않고 개체에 엑세스 하고자 할 경우 사용
 : shared_ptr instance 사이에서 순환 참조가 발생할 경우 shared_ptr가 삭제되지 않는 경우 발생
- weak_ptr 자체는 참조 수 계산에 참가하지 않으므로 shared_ptr가 유지되는 경우에서만 사용 가능함
 : 개체가 삭제된 경우 bad_weak_ptr 예외가 발생된다.


예제 :

https://msdn.microsoft.com/ko-kr/library/hh279672.aspx#Anchor_0

번역으로 본문의 결과가 좀 이상한데 실제 실행해 보면 다음과 같다.

Creating Controller0
Creating Controller1
Creating Controller2
Creating Controller3
Creating Controller4
push_back to v[0]: 1
push_back to v[0]: 2
push_back to v[0]: 3
push_back to v[0]: 4
push_back to v[1]: 0
push_back to v[1]: 2
push_back to v[1]: 3
push_back to v[1]: 4
push_back to v[2]: 0
push_back to v[2]: 1
push_back to v[2]: 3
push_back to v[2]: 4
push_back to v[3]: 0
push_back to v[3]: 1
push_back to v[3]: 2
push_back to v[3]: 4
push_back to v[4]: 0
push_back to v[4]: 1
push_back to v[4]: 2
push_back to v[4]: 3
use_count = 1
Status of 1 = On
Status of 2 = On
Status of 3 = On
Status of 4 = On
use_count = 1
Status of 0 = On
Status of 2 = On
Status of 3 = On
Status of 4 = On
use_count = 1
Status of 0 = On
Status of 1 = On
Status of 3 = On
Status of 4 = On
use_count = 1
Status of 0 = On
Status of 1 = On
Status of 2 = On
Status of 4 = On
use_count = 1
Status of 0 = On
Status of 1 = On
Status of 2 = On
Status of 3 = On
Destroying Controller0
Destroying Controller1
Destroying Controller2
Destroying Controller3
Destroying Controller4

Controller class는 내부에 others weak_ptr를 가지고 있고
RunTest()에서 다른 shared_ptr들을 others에 삽입하여 참조하도록 하였음.

Controller class 내부의 others를 shared_ptr로 바꾸면
각각의 use_count는 5가 되고 순환 참조로 인해 Controller들이 삭제 되지 않는다.



Modern Effective C++에서 관련된 chapter는 다음과 같고
더 자세한 내용을 원한다면 책을 꼭 읽는 것을 추천.
단 Modern Effective C++이 처음이라면 Effective C++을 먼저 읽는 것을 추천.

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14
http://a.co/0MIIC9k

Item 20: Use std::weak_ptr for std::shared_ptr- like pointers that can dangle.


- std::shared_ptr와 거의 유사하지만 참조 횟수에 영향을 미치지 않고
  지칭하는 객체를 잃은 상황을 확인할 수 있음.

std::shared_ptr<Widget> spw1 = wpw.lock();
auto spw2 = wpw.lock();
std::shared_ptr<Widget> spw3(wpw);

wpw이 expired 되었다면 spw1, spw2는 null이고
spw3를 만들때는 std::bad_weak_ptr exception 발생

아래는 책에서 말하는 loadWidget caching code임

std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
   static std::unordered_map<WidgetID, std::weak_ptr<const Widget>> cache;
   auto objPtr = cache[id].lock();
      if (!objPtr) {
         objPtr = loadWidget(id);
         cache[id] = objPtr;
      }
   return objPtr;
}

cache에 있는 widget이 만료되지 않았다면 (lock() != nullptr) cache된 widget을 제공.

- shared_ptr 개체를 참조할 때 순환 참조를 피하고자 사용하지만 이는 일반적인 상황은 아님,,,

C++ 11/14 관련 내용 대충 정리, shared_ptr

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.

C++11/14/17 기능에 대한 지원(최신 C++)
https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


shared_ptr

https://msdn.microsoft.com/ko-kr/library/hh279669.aspx


- 여러곳에서 참조하는 개체의 수명을 관리하기 위한 C++ STL 스마트 포인터
- shared_ptr 를 초기화 한 후에 복사, 함수 인수를 값으로 전달 및 다른 shared_ptr 인스턴스로 할당 가능
- 참조 횟수가 0에 도달하면 참조개체와 내부 제어 블록을 삭제





    // Use make_shared function when possible.
    auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");

    // Ok, but slightly less efficient. 
    // Note: Using new expression as constructor argument
    // creates no named variable for other code to access.
    shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));

    // When initialization must be separate from declaration, e.g. class members, 
    // initialize with nullptr to make your programming intent explicit.
    shared_ptr<Song> sp5(nullptr);
    //Equivalent to: shared_ptr<Song> sp5;
    //...
    sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");

- 되도록이면 예외 발생 상황에서 문제 없는 make_shared를 사용하여 만들어라.

    vector<shared_ptr<MediaAsset>> assets;

    assets.push_back(shared_ptr<Song>(new Song(L"Himesh Reshammiya", L"Tera Surroor")));
    assets.push_back(shared_ptr<Song>(new Song(L"Penaz Masani", L"Tu Dil De De")));
    assets.push_back(shared_ptr<Photo>(new Photo(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")));

    vector<shared_ptr<MediaAsset>> photos;

    copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
    {
        // Use dynamic_pointer_cast to test whether
        // element is a shared_ptr<Photo>.
        shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);  
        return temp.get() != nullptr;
    });

    for (const auto&  p : photos)
    {
        // We know that the photos vector contains only 
        // shared_ptr<Photo> objects, so use static_cast.
        wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location_ << endl;
    }

- dynamic_pointer_cast, static_pointer_cast, const_pointer_cast를 사용하여 shared_ptr를 캐스팅할 수 있음.

- shared_ptr는 다음의 방법으로 다른 함수에 전달할 수 있음.
 : 값(value)으로 전달 (복사 생성자 호출 => 참조 횟수 증가, 약간 오버 헤드 있음)
 : 참조(reference), const 참조로 전달 (참조횟수 증가 안함.)
 : 내부 포인터 또는 내부 개체에 대한 참조를 전달 (개체 공유, 단 참조 횟수는 증가하지 않음, 좀 위험.)

- shared_ptr 전달 방법을 결정할 때 고려 해야 하는 것은 호출 수신자(callee)가 소유권을 공유할 수 있는지를 판단해야 함.
 : shared_ptr가 call이 완료 될 때 까지 caller, callee 외 다른 소유자로 인해 shared_ptr의 유효함이 보장되면 값으로 전달하고, 보장되지 않는 경우라면 참조를 넘겨 callee가 알아서 판단하도록 한다.
- std:vector<shared_ptr<T>>에서 람다 식 본문 또는 명명된 함수 개체로 전달할 경우 람다, 함수 개체가 포인터를 저장하지 않는다면 참조로 전달한다.


 // Initialize two separate raw pointers.
    // Note that they contain the same values.
    auto song1 = new Song(L"Village People", L"YMCA");
    auto song2 = new Song(L"Village People", L"YMCA");

    // Create two unrelated shared_ptrs.
    shared_ptr<Song> p1(song1);    
    shared_ptr<Song> p2(song2);

    // Unrelated shared_ptrs are never equal.
    wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
    wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;

    // Related shared_ptr instances are always equal.
    shared_ptr<Song> p3(p2);
    wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl; 

- 가리키는 개체의 값을 비교하는 것이 아니라  shared_ptr이 가리키는 개체를 비교해야 함.


Modern Effective C++에서 관련된 chapter는 다음과 같고
더 자세한 내용을 원한다면 책을 꼭 읽는 것을 추천.
단 Modern Effective C++이 처음이라면 Effective C++을 먼저 읽는 것을 추천.

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14
http://a.co/0MIIC9k

Item 19: Use std::shared_ptr for shared-ownership resource management.
Item 21: Prefer std::make_unique and std::make_shared to direct use of new.

- std::shared_ptr의 크기는 생 포인터의 두배
 : custom deleter를 지정해도 std::shared_ptr 객체의 크기가 변하지 않음.
- 제어 블럭에서 reference count, weak reference count, custom deleter 등을 저장함.
 : std::make_shared는 항상 제어 블럭을 생성
 : std::unique_ptr, std::auto_ptr로 부터 std:shared_ptr 을 생성하면 제어 블록이 생성
 : raw pointer로 std::shared_ptr 생성하면 제어 블록이 생성
   단 하나의 raw pointer에서 여러 std::shared_ptr을 생성하면 문제 발생.
- std::shared_ptr로 관리되는 클래스에서 클래스의 this로 부터 shared_ptr을 생성할 때는 shared_from_this()를 사용해야 순환 참조 문제가 없음.
- 참조 횟수의 증가와 감소가 반드시 원자적 연산이어야 함.

C++ 11/14 관련 내용 대충 정리, unique_ptr

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.

C++11/14/17 기능에 대한 지원(최신 C++)
https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


unique_ptr

https://msdn.microsoft.com/ko-kr/library/hh279676.aspx

- 일반 C++ 개체에 대한 스마트 포인터가 필요할 경우 사용
- unique_ptr은 포인터를 공유 하지 않고 이동만 가능함.
 : 이동 시 메모리 리소스의 소유권이 이전되어 원래 unique_ptr는 더 이상 소유하지 않음.
- 다른 unique_ptr로 복사, 함수에 값으로 전달하거나 사본이 필요한 STL(표준 템플릿 라이브러리) 알고리즘에서는 사용 불가
- unique_ptr을 생성할 경우 되도록이면 make_unique 도우미 함수를 사용
 : c++ 14 지원을 위해 compile option(-std=c++14
- <memory> 헤더의 std namespace에 정의 되어 있음.





unique_ptr<Song> SongFactory(const std::wstring& artist, const std::wstring& title)
{
    // Implicit move operation into the variable that stores the result.
    return make_unique<Song>(artist, title);
}

void MakeSongs()
{
    // Create a new unique_ptr with a new object.
    auto song = make_unique<Song>(L"Mr. Children", L"Namonaki Uta");

    // Use the unique_ptr.
    vector<wstring> titles = { song->title };

    // Move raw pointer from one unique_ptr to another.
    unique_ptr<Song> song2 = std::move(song);

    // Obtain unique_ptr from function that returns by value.
    auto song3 = SongFactory(L"Michael Jackson", L"Beat It");
}

일반적인 사용 예제, song 변수가 가졌던 개체는 song2로 이전(move)되어 song의 member들은 nullptr이 됨.

void SongVector()
{
    vector<unique_ptr<Song>> songs;

    // Create a few new unique_ptr<Song> instances
    // and add them to vector using implicit move semantics.
    songs.push_back(make_unique<Song>(L"B'z", L"Juice"));
    songs.push_back(make_unique<Song>(L"Namie Amuro", L"Funky Town"));
    songs.push_back(make_unique<Song>(L"Kome Kome Club", L"Kimi ga Iru Dake de"));
    songs.push_back(make_unique<Song>(L"Ayumi Hamasaki", L"Poker Face"));

    // Pass by const reference when possible to avoid copying.
    for (const auto& song : songs)
    {
        wcout << L"Artist: " << song->artist << L"   Title: " << song->title << endl; 
    }    
}

이동이 불가하므로 vector 로 바로 생성된 개체를 전달함.
vector내 개체를 접근하기 위해 const auto& 로 reference를 참조함.
(const auto song : songs) 은 unique_ptr을 복사하려는 것이므로 컴파일 에러 발생


// Create a unique_ptr to an array of 5 integers.
 auto p = make_unique<int[]>(5);

 // Initialize the array.
 for (int i = 0; i < 5; ++i)
 {
  p[i] = i;
  wcout << p[i] << endl;
 }

배열에 대한 unique_ptr를 make_unique를 사용해서 생성할 수 있지만 초기화는 별도로..


Modern Effective C++에서 관련된 chapter는 다음과 같고
더 자세한 내용을 원한다면 책을 꼭 읽는 것을 추천.
단 Modern Effective C++이 처음이라면 Effective C++을 먼저 읽는 것을 추천.

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14
: http://a.co/0MIIC9k

Item 18:Use std::unique_ptr for exclusive-ownership resource management.
Item 21:Prefer std::make_unique and std::make_shared to direct use of new.


참고할 만한 것들을 보면

아래와 같이 Investment를 상속하는 여러 class들이 있을 경우

class Investment { … };
class Stock:  public Investment { … };
class Bond:  public Investment { … };
class RealEstate:  public Investment { … };

template<typename... Ts>                // return std::unique_ptr std::unique_ptr<Investment>             // to an object created 
makeInvestment(Ts&&... params);       // from the given args

이렇게 factory 함수인 makeInvestment를 만들어 Investment를 가지는 unique_ptr를 제공할 수 있음.

{
   ...
   auto pInvestment =                // pInvestment is of type     
   makeInvestment( arguments );  // std::unique_ptr<Investment>
   ...
}

이렇게 사용을 하게 될 것이고 생성된 pInvestment는 변수의 scope을 벗어나게 되면 자동으로 소멸 되게 되는게 일반적인 사용 시나리오 일 것임.

함께 unique_ptr는 기본적으로 개체의 소멸자를 호출하겠지만 custom deleter를 직접 지정할 수 있음.

auto delInvmt = [](Investment* pInvestment)       // custom
                      {                                          // deleter
                         makeLogEntry(pInvestment);   // (a lambda
                         delete pInvestment;              // expression)
                      };
template<typename... Ts>                                // revised
std::unique_ptr<Investment, decltype(delInvmt)>   // return type
makeInvestment(Ts&&... params)


그리고 c++14의 auto를 사용하게 된다면.. 좀 더 간편할 것임.

template<typename... Ts>
auto makeInvestment(Ts&&... params)              // C++14

다만 일반적인 unique_ptr의 사이즈는 raw pointer와 동일하겠지만 custom deleter를 가진 unique_ptr의 사이즈는 한두 word의사이즈가 추가된다. 함께 custom deleter로 function object를 사용하는것 보다 stateless function object인 lambda를 사용하는 것이 unique_ptr의 사이즈를 늘리지 않는다고 함.

그리고 unique_ptr를 생성할 때는 new 대신 std::make_unique를 사용하는 것을 추천하고 있음.
다만 std::make_unique는 c++14에서 지원하고 있어 compile option 설정 필요.


C++ 11/14 관련 내용 대충 정리, 스마트 포인터

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.


C++11/14/17 기능에 대한 지원(최신 C++)
https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


스마트 포인터

: https://msdn.microsoft.com/ko-kr/library/hh438480.aspx


- <memory>  헤더의 std 네임스페이스에 정의
- RAII (Resource Acquisition Is Initialialization) 위해 사용 가능
 : RAII의 기본 원칙은 힙 할당 리소스(예: 동적 할당 메모리 또는 시스템 개체 핸들)의 소유권을 해당 소멸자가 리소스를 삭제, 정리하는 코드를 가진 스택 할당 개체를 제공
- 생성한 개체를 스마트 포인터로 즉시 전달하는 것을 추천.
- 소유권 혼동 여지가 없는 제한된 범위, 루프 또는 지원 함수의 작은 코드 블록에서만 사용


void UseRawPointer()
{
    // Using a raw pointer -- not recommended.
    Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); 

    // Use pSong...

    // Don't forget to delete!
    delete pSong;   
}


void UseSmartPointer()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));

    // Use song2...
    wstring s = song2->duration_;
    //...

} // song2 is deleted automatically here.


class LargeObject
{
public:
    void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

} //pLarge is deleted automatically when function block goes out of scope.


스마트 포인터를 사용하는 필수 단계
  1. 스마트 포인터를 자동(지역) 변수로 선언합니다. (스마트 포인터 자체에 new 또는 malloc 식을 사용하지 마십시오.)
  2. 형식 매개 변수에서 캡슐화된 포인터가 가리키는 대상의 형식을 지정합니다.
  3. 원시 포인터를 스마트 포인터 생성자의 new-ed 개체에 전달합니다. (일부 유틸리티 기능 또는 스마트 포인터 생성자로 이 작업을 자동으로 수행할 수 있습니다.)
  4. 오버로드된 -> 및 * 연산자를 사용하여 개체에 액세스합니다.
  5. 스마트 포인터가 소멸 될 때 개체가 함께 삭제됨.

void SmartPointerDemo2()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Free the memory before we exit function block.
    pLarge.reset();

    // Do some other work...

}

. -> 로는 스마트 포인터가 가진 개체를 참조하고 .은 스마트 포인터의 멤버함수를 참조한다.
. .get()으로 원시 포인터를 전달 할 수 있음. (언제 필요할까?)


스마트 포인터의 종류


C++ 표준 라이브러리 스마트 포인터
. POCO(Plain Old C++ Object)에 대한 포인터를 캡슐화 하는 데 가장 먼저 스마트 포인터를 사용함.

. unique_ptr
 : 한 명의 소유자만 허용함, 새 소유자로 이동할 수 있지만 복사 불가.
 : 작고 효율적이며 크기는 1 포인터
 : STL 컬렉션에서 빠른 삽입 및 검색을 위해 rvalue 참조 지원

. shared_ptr
 : 참조 횟수가 계산되는 스마트 포인터, 하나의 원시 포인터 하나를 여러 소유자에게 할당 하려고 할 경우(컨테이너에서 포인터 복사본을 반환 시 원본을 유지하고 싶을 때)
 : 원시포인터는 모든 소유자가 없어지기 전까지 삭제되지 않는다.
 : 크기는 2 포인터, 하나는 개체용, 다른 하나는 참조 횟수를 위해

. weak_ptr
 : shared_ptr 와 함께 사용할 수 있는 스마트 포인터
 : shared_ptr의 개체를 weak_ptr로 참조해도 shared_ptr의 참조 카운트가 증가하지 않음.
 : 개체가 삭제 되지 않게 되는 상황인 shared_ptr의 상호 참조를 차단하기 위해 사용


C++ 11/14 관련 내용 대충 정리, 개체 수명 및 리소스 관리

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.

C++11/14/17 기능에 대한 지원(최신 C++)
https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


개체 수명 및 리소스 관리

https://msdn.microsoft.com/ko-kr/library/hh438473.aspx


- managed language와는 달리 C++은 gabage collection(GC)이 없음.
- C++에서 resource 관리는 object lifetime과 밀접한 관련이 있음
 : memory, non-memory resources들은 소멸자를 통해서 관리 될 수 있음.


개념

- object-lifecycle 관리에서 중요한 것은 캡슐화임.
 : object가 소유한 resource는 무엇이고 어떻게 해제하는지를 알 필요가 없고
   그냥 object를 삭제하면 끝이여야 함.
 : C++ 에서 개체가 scope을 벗어날 때
   정해진 시간에 생성된 역순으로 소멸되며
   정해진 순서에 따라 개체의 부모 개체와 멤버 변수들이 소멸된다.
 : smart pointers(unique_ptr, shared_ptr)나 STL container들도 new/delete, new[]/delete[]를 캡슐화 하고 있듯이 중요함.

- 소멸자(Destructor)는 resource release 측면에서 중요하다.
 : Memory나 files, sockets과 같은 non-memory resources들을 개체에서
   필요에 의해 사용할 경우, 개체가 소멸될 때 소멸자에서 사용한 resource들을 해제하는 것은 당연하다.

- 프로그램에서 개체의 소유는 DAG(Directed Acylic Graph, 방향을 가지되 자신으로 돌아 올 수 없는) 형태여야 한다.
 : DAG의 형태로 순환 참조가 없는 소유 형태에서 각 노드가 소멸자를 가진 개체가 된다면 resource leak이 발생할 수 없다.


Heap-based lifetieme


heap 기반의 개체 관리는 smart pointer(shared_ptr)를 사용하고 순환 참조를 피하기 위해서는 weak_ptr를 사용하라.

void func() {  
  
auto p = make_shared<widget>(); // no leak, and exception safe  
...  
p->draw();   
  
} // no delete required, out-of-scope triggers smart pointer destructor  
  

하나뿐인 소유권 관리를 위해서는 unique_ptr와 Pimpl idiom을 사용할 수 있다.

unique_ptr<widget> p(new widget()); 

또한 소유하지 않는 관찰의 측면에서 raw pointer를 사용할 수 있으나 leak은 없지만 dangle pointer가 될 수 있음.

class node {  
  ...  
  vector<unique_ptr<node>> children; // node owns children  
  node* parent; // node observes parent, which is not a concern  
  ...  
};  
node::node() : parent(...) { children.emplace_back(new node(...) ); }  
  


Stack-based lifetime


modern C++에서는 stack-based scope은 높은 효율성을 갖는 stack lifetime, data member lifetime을 기반한 강력한 코드를 작성하는데 효과적인 방법이다. 이에 반해  raw pointer를 사용하여 heap 기반 object lifetime를 관리하는 방법은 leak과 비효율적이다.

class widget {  
private:  
  gadget g;   // lifetime automatically tied to enclosing object  
public:  
  void draw();  
};  
  
void functionUsingWidget () {  
  widget w;   // lifetime automatically tied to enclosing scope  
              // constructs w, including the w.g gadget member  
  …  
  w.draw();  
  …  
} // automatic destruction and deallocation for w and w.g  
  // automatic exception safety,   
  // as if "finally { w.dispose(); w.g.dispose(); }"  
  

 단  global static, local static에서의 예외 상황은 디버그 하기 힘든 경우가 있고 동시성 측면에서 안전하지 않고 다형성을 사용할 경우 객채의 생성, 소멸 관련 문제가 발생할 수 있음.


C++ 11/14 관련 내용 대충 정리, RAII

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.

C++11/14/17 기능에 대한 지원(최신 C++)
https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


Resource Acquisition Is Initialization (RAII)

https://msdn.microsoft.com/ko-kr/library/hh438480.aspx

이미 많이 사용하는 거라..
정리까지는 필요 없겠지만, 위 페이지에는 자세한 내용이 없어서..

http://en.cppreference.com/w/cpp/language/raii
https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization


- C++ 기술로써 resource 사용을 위한 획득, 제거를 개체의 life cycle에 맞춰 binding 하는 방법
 : resource의 예 :  allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply
- Resource는 class로 캡슐화됨
 : 생성자에서 resource를 획득하고  RAII instance가 생성되거나 예외를 발생 시킨다.
 : 소멸자에서 resoruce를 해제하되 예외를 발생시키지는 않음.
- Resource는 RAII class를 통해 사용되며 RAII instance의 lifetime 동안 사용 가능함.
- RAII는 개체를 사용할 동안 resource를 접근하는 것과 개체가 소멸될 때 획득한 resource는 해제됨을 보장


std::mutex m;
 
void bad() 
{
    m.lock();                    // acquire the mutex
    f();                         // if f() throws an exception, the mutex is never released
    if(!everything_ok()) return; // early return, the mutex is never released
    m.unlock();                  // if bad() reaches this statement, the mutex is released
}
 
void good()
{
    std::lock_guard<std::mutex> lk(m); // RAII class: mutex acquisition is initialization
    f();                               // if f() throws an exception, the mutex is released
    if(!everything_ok()) return;       // early return, the mutex is released
}                                      // if g

- C++ library class는 각기 자신들의 생성자, 소멸자를 통해 resource를 관리함 (std::string, std::vector, std::thread)
- C++ library class는 RAII wrapper를 제공함
 : std::unique_ptr, std::shared_ptr : 동적으로 생성된 memory를 관리
 : std::lock_guard, std::unique_lock, std::shared_lock : mutex 관리


2018년 5월 17일 목요일

C++ 11/14 관련 내용 대충 정리, 균일 초기화, 생성자 위임

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.
역시 설명은 MSDN이 갑이다..

C++11/14/17 기능에 대한 지원(최신 C++)
https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


균일 초기화 및 생성자 위임

https://msdn.microsoft.com/ko-kr/library/dn387583.aspx


중괄호 초기화

https://msdn.microsoft.com/ko-kr/library/dn387583.aspx#Anchor_0

. 클래스, 구조체, 공용 구조체를 대상으로 중괄호 초기화 사용 가능
. 암시적, 명시적으로 선언된 기본 생성자가 있는 경우 중괄호 초기화 사용 가능
. 기본생성자가 삭제(=delete)된 경우, 중괄호 기본 초기화를 사용 할 수 없음.
. 초기화 할 있는 곳(함수 매개 변수, 반환값, new 키워드)에서 사용 가능

#include <string>  
using namespace std;  
  
class class_a {  
public:  
    class_a() {}  
    class_a(string str) : m_string{ str } {}  
    class_a(string str, double dbl) : m_string{ str }, m_double{ dbl } {}  
double m_double;  
string m_string;  
};  
  
int main()  
{  
    class_a c1{};  
    class_a c1_1;  
  
    class_a c2{ "ww" };  
    class_a c2_1("xx");  
  
    // order of parameters is the same as the constructor  
    class_a c3{ "yy", 4.4 };  
    class_a c3_1("zz", 5.5);  
}  

* 클래스에 기본 생성자가 없는 경우 중괄호 초기화에 클래스 멤버가 나타나는 순서는 멤버가 나타나는 순서이고 건너 뛸 수 없음.

class class_d {  
public:  
    float m_float;  
    string m_string;  
    wchar_t m_char;  
};  
  
int main()  
{  
    class_d d1{};  
    class_d d1{ 4.5 };  
    class_d d2{ 4.5, "string" };  
    class_d d3{ 4.5, "string", 'c' };  
  
    class_d d4{ "string", 'c' }; // compiler error  
    class_d d5("string", 'c', 2.0 }; // compiler error  
}   

class_d* cf = new class_d{4.5};  
kr->add_d({ 4.5 });  
return { 4.5 };  


위임 생성자

https://msdn.microsoft.com/ko-kr/library/dn387583.aspx#Anchor_2

. 다른생성자에 위임하는 생성자의 멤버를 초기화 할 수는 없음.

class class_c {  
public:  
    int max;  
    int min;  
    int middle;  
  
    class_c(int my_max) {   
        max = my_max > 0 ? my_max : 10;   
    }  
    class_c(int my_max, int my_min) : class_c(my_max) {   
        min = my_min > 0 && my_min < max ? my_min : 1;  
    }  
    class_c(int my_max, int my_min, int my_middle) : class_c (my_max, my_min){  
        middle = my_middle < max && my_middle > min ? my_middle : 5;  
}  
};  
int main() {  
  
    class_c c1{ 1, 3, 2 };  
}  


C++ 11/14 관련 내용 대충 정리, 형식시스템

최신 C++을 공부하려 책을 보긴 했었는데
내용이 기존 문법과 함께 여기저기 흩어져 있어 보기에 좀 어려워
MSDN을 참고하여 대충 정리하려함.
역시 설명은 MSDN이 갑이다..

C++11/14/17 기능에 대한 지원(최신 C++)
: https://msdn.microsoft.com/ko-kr/library/hh567368.aspx


C++ 형식 시스템

https://msdn.microsoft.com/ko-kr/library/hh279663.aspx

이제 auto를 사용하자.

값형식

: https://msdn.microsoft.com/ko-kr/library/hh438479.aspx

Value vs. reference types


* copy by value, copy by reference에 대한 개념 때문인지 내용이 어색 하기는 함.

- 본문에서 C++ class는 value type이지만 객체지향(OOP)를 지원하고 다형성 동작을 가능하도록 reference type으로 지정할 수도 있음.
- Value type은 메모리, 레이아웃 제어와 관련이 있고 reference type은 다형성을 위한 base class와 가상함수(virtual functions)와 관련이 있음.
- Value type은 copyable (복사생성자-copy constructor, 복사연산자copy assignment operator 활성화)하지만 Reference type은 non-copyable(복사생성자, 복사연산자 비활성화)하고 다형성을 지원하는 가상 소멸자(virtual destructor)를 사용함.
- Value type은 위의 특성 상 복사가능하고 각기 독립적인 값을 제공하는 contents 용도로 주로 사용되고 reference type은 다형성의 측면에서 object의 identity를 나타내며 polymorphic type이라고도 불린다. (??)

아래는 reference type의 예

class MyRefType {  
private:  
    MyRefType & operator=(const MyRefType &);  
    MyRefType(const MyRefType &);  
public:  
    MyRefType () {}  
};  
  
int main()  
{  
    MyRefType Data1, Data2;  
    // ...  
    Data1 = Data2;  
}  

: 복사생성자와 복사연산자를 private로 지정하여 복사할 수 없게 만들었음.

Value types and move efficiency


새로운 복사 최적화로 인해 복사 할당 오버헤드가 방지됨.

컴파일러의 copy allocation overhead 방지를 위해 이동 생성자를 생성 될 수 있는데 적절한 이동을 위해서는 사용자가 직접 &&를 사용하여 이동 생성자와 이동 연산자를 정의해야 한다.
복사 생성자를 사용할 필요가 있다면 이동 생성자를 정의하는 것이 deep copy보다 효율적임. 또한 이동을 고려하되 복사를 방지하고자 할 경우 unique_ptr를 사용하는 것도 방법임.
이동 문법은 함수에서 값을 리턴하거나 container에 삽입하는 상황에서 사용 될 수 있어 복사보다 효율적임.

 
#include <set>  
#include <vector>  
#include <string>  
using namespace std;  
  
//...  
set<widget> LoadHugeData() {  
    set<widget> ret;  
    // ... load data from disk and populate ret  
    return ret;  
}  
//...  
widgets = LoadHugeData();   // efficient, no deep copy  
  
vector<string> v = IfIHadAMillionStrings();  
v.insert( begin(v)+v.size()/2, "scott" );   // efficient, no deep copy-shuffle  
v.insert( begin(v)+v.size()/2, "Andrei" );  // (just 1M ptr/len assignments)  
//...  
HugeMatrix operator+(const HugeMatrix& , const HugeMatrix& );  
HugeMatrix operator+(const HugeMatrix& ,       HugeMatrix&&);  
HugeMatrix operator+(      HugeMatrix&&, const HugeMatrix& );  
HugeMatrix operator+(      HugeMatrix&&,       HugeMatrix&&);  
//...  
hm5 = hm1+hm2+hm3+hm4+hm5;   // efficient, no extra copies  

value-like class에서도 deep copy 보다 move를 사용하는 것이 효율적일 수 있고 이동 생성자, 이동 연산자를 생성하여 적절히 이동 될 수 있도록 할 수 있다.

#include <memory>  
#include <stdexcept>  
using namespace std;  
// ...  
class my_class {  
    unique_ptr<BigHugeData> data;  
public:  
    my_class( my_class&& other )   // move construction  
        : data( move( other.data ) ) { }  
    my_class& operator=( my_class&& other )   // move assignment  
    { data = move( other.data ); return *this; }  
    // ...  
    void method() {   // check (if appropriate)  
        if( !data )   
            throw std::runtime_error("RUNTIME ERROR: Insufficient resources!");  
    }  
};  
  



형식 변환 및 형식 안정성


https://msdn.microsoft.com/ko-kr/library/hh279667.aspx

암시적 형식 변환(Implicit conversion)


당연하겠지만 확대 변환은 데이터의 손실, 축소 변환은 데이터 손실 발생 가능.


명시적 변환(Explicit conversion, casts)


아래는 C 스타일 캐스트 연산자임.

(int) x; // old-style cast, old-style syntax  
int(x); // old-style cast, functional syntax  

왠만하면 static_cast, dynamic_cast, const_cast, reinterpret_cast 를 사용하자.

static_cast : 컴파일 시점에서 문제 없을 경우 캐스팅

double d = 1.58947;  
int i = d;  // warning C4244 possible loss of data  
int j = static_cast<int>(d);       // No warning.  
string s = static_cast<string>(d); // Error C2440:cannot convert from  
                                   // double to std:string  

// No error but not necessarily safe.  
Base* b = new Base();  
Derived* d2 = static_cast<Derived*>(b);  

dynamic_cast : 런타임시 기본 타입에서 파생 타입으로 캐스팅, 일부 오버헤드 있음.

Base* b = new Base();  

// Run-time check to determine whether b is actually a Derived*  
Derived* d3 = dynamic_cast<Derived*>(b);  

// If b was originally a Derived*, then d3 is a valid pointer.  
if(d3)  
{  
   // Safe to call Derived method.  
   cout << d3->DoSomethingMore() << endl;  
}  
else  
{  
   // Run-time check failed.  
   cout << "d3 is null" << endl;  
}  

//Output: d3 is null;  

const_cast : const<->비const로 캐스팅, 근데 사용하는 건 좀..

void Func(double& d) { ... }  
void ConstCast()  
{  
   const double pi = 3.14;  
   Func(const_cast<double&>(pi)); //No error.  
}  

reinterpret_cast : 그냥 캐스팅
아래는 관련 캐스팅의 차이

const char* str = "hello";  
int i = static_cast<int>(str);//error C2440: 'static_cast' : cannot  
                              // convert from 'const char *' to 'int'  
int j = (int)str; // C-style cast. Did the programmer really intend  
                  // to do this?  
int k = reinterpret_cast<int>(str);// Programming intent is clear.  
                                   // However, it is not 64-bit safe.