카테고리 없음

C++언어 강좌(공부) #

바래다주기 2024. 5. 8. 00:17

21. 예외 처리 및 C++11

예외 처리 키워드

  1. mutable : const 함수의 경우 멤버 변수에 대한 가공이 불가능한 읽기 전용 함수임
    하지만 mutable로 선언된 멤버 변수에 대해서는 const함수 내에서도 가공 가능

ex)
class CTest
{
private:
       int m_iA;
       mutable int m_iB;

 

public:
     void Change() const
    {
            m_iA = 100; // -> 기존의 const의 취지에 맞게 변경 불가
            m_iB = 200; // -> mutable 키워드 사용 시 변경 가능
     }
};

 

2. try_catch
ex)
int iFirst, iSecond;
cin >> iFirst >> iSecond;

try               // 해당 try문 먼저 실행
{
        if (0 == iSecond) throw iSecond;
        // 해당 조건 만족시 throw 키워드를 이용하여 catch에 매개변수로 넘겨줌
        cout << iFirst / iSecond << endl << iFirst % iSecond << endl;
}


catch (int expn)
{
        cout << "fail" << endl;
}

 

auto
ex)
auto a = "A";
auto b = 10.3f;
auto c = 112;

 

vector vecAuto;

// 아래 둘 다 같은 방식으로 반복자를 선언 (pair 객체도 이런 방식으로 만들 수 있다)
auto iterAuto = vecAuto.begin();
vector::iterator iterIter = vecAuto.begin();

 

자료형을 입력하지 않아도 스스로 판단하여 자료형을 적용 시켜주는 키워드
포인터 형도 사용이 가능함 초기화 식을 참조하여 해당 변수의 type을 정해준다
직관적이지 못하다는 점이 단점이라면 단점.

 

범위 기반 for
ex)
int Array[5] = { 1,2,3,4,5 };
for (int i : Array)
            cout << i << endl;

 

vector vecFor;
vecFor.push_back(11);
vecFor.push_back(22);
vecFor.push_back(33);
for (auto j : vecFor)
              cout << j << endl;

 

// 아래 형식을 사용해 컨테이너의 원소에 직접 접근하여 변경도 가능
for (auto& k : vecFor)
              cout << (k += 5) << endl;

 

유니폼 초기화
ex)
int uniArray[5]{ 111,222,333,444,555 };
vectorvecUni{ 666,777,888 };

 

블록 단위 데이터에 한해 원소들을 블록 데이터로 묶어서 대입하는 방식
vector의 경우 유니폼 초기화를 진행한 원소의 수만큼 Capcity가 잡히며, 이후 정책에 따라 증가

 

블록 단위 데이터인 구조체와 클래스 에서도 해당 방식이 적용되지만
클래스의 경우 해당 값을 넘겨 받을 매개변수 생성자가 필요하다
ex)
class CTest
{
private:
       int iA;
       int iB;

public:
       CTest(int _iA, int _iB) {}
};

 

void main()
{
      CTest test{ 10, 20 };
}

 

람다식
함수 객체의 단점을 극복하기 위해 생겨난 문법
함수 객체의 단점 : 1. 함수를 직접 작성해야 하므로 용이하지 못함
                               2. 일반 함수와 부분이 어렵기에 가독성이 떨어짐
                               But 생성자의 매개변수를 통해 외부의 데이터를 받아와서 내부에서 가공하는 방식이 가능
                               함수 객체에 비해 람다식은 inline함수로, 메모리에 할당되지 않으며 생성자 / 소멸자가 호출되는 함수                                   객체에 비해 속도가 빠르다는 특지을 가짐
                               람다 식은 연산이 빠르며 직관적이라는 장점이 있지만, 미리 클래스를 만들어두고 필요시마다 가져다                                 사용하는 함수 객체와 달리 그자리에서 바로 생성해야 하기때문에 내부 코드 노출 우려

 

ex)
// 반환 타입 X, 매개변수 X, 출력하는 일반 함수와 동일
[]() {cout << "hello world!" << endl; }();

 

// 반환 타입 X, 정수형 n을 매개변수로 받아 출력하는 일반 함수와 동일
[](int n) {cout << n << endl; }(100);

 

// 반환 타입 X, default 매개변수를 가지는 일반 함수와 동일
[](int iSour = 200) {cout << iSour << endl; }();

 

// []()->type {} 형태로 지정하므로써 명시적 형 반환이 가능하다.
int iResult = [](int a, int b) {return a + b; }(10, 20);
int iResult2 = [](int a, int b)->int {return a + b; }(30, 40);

 

[ ] : 람다 소개자 / 캡처절 => 컴파일러는 해당 기호를 보고 람다식 여부 판단
( ) : 파라미터(매개변수)지정자 => 일반 함수의 매개 변수를 받는 방식과 동일
{ } : 람다 몸체 => 일반 함수의 몸체와 동일

 

람다식 - 캡처절
람다 소개자인 [ ]는 캡처절 이라고도 불린다
(Capture : 남의 것을 자신의 것으로 만드는 행위)
ex)
int iFirst = 11, iSecond = 22;

 

// 값을 복사하는 캡처 -> 해당 캡처값에 대해 const화가 일어나기 때문에 변경이 불가능
[iFirst, iSecond]() {cout << iFirst << endl << iSecond << endl; }();

 

// 방법 1. mutable 키워드를 사용하여 const화 해제 -> const 키워드는 해제되지만, 값 복사이므로 원본 값은 변화 X
[iFirst, iSecond]() mutable {
                                                     iFirst += 10;
                                                     iSecond += 20;
}();

cout << iFirst << endl << iSecond << endl;

 

// 방법 2. 레퍼런스를 사용하여 값에 직접 접근 -> 실제 참조에 접근하므로 원본 값을 가공할 수 있다.
[&iFirst, &iSecond]() {
                                                    iFirst += 10;
                                                    iSecond += 20;
}();
cout << iFirst << endl << iSecond << endl;

 

[ ] : 캡처를 진행하지 않는, 일반 람다 소개자
[&X] : 외부의 X라는 변수에 대해 래퍼런스에 대한 참조 형식으로 캡처
[X] : 외부의 X라는 변수에 대해 값에 대한 호출 형식으로 캡처
[&] : 모든 외부 변수를 래퍼런스에 대한 참조 형식으로 캡처
[=] : 모든 외부 변수를 값에 대한 호출 형식으로 캡처
[&X, Y] : X라는 변수는 래퍼런스에 대한 참조 형식으로, Y라는 변수는 값에 대한 호출 형식으로 캡처
[&, X] : 모든 외부 변수를 래퍼런스에 대한 참조 형식으로 캡처하되, X는 값에 대한 호출
[=, &X] : 모든 외부 변수를 값에 대한 호출 형식으로 캡처하되, X는 래퍼런스에 대한 참조
((예외 사항))
[&, =] : 둘 중 하나만을 선택해서 캡처를 진행해야 함
[&X, =] : 모든 외부 변수에 대한 참조 방식은 앞에 존재해야 함
[X, &] : 위 예외사항과 마찬가지

 

R-Value Reference
기존의 방식

  1. L-Value : 메모리에 등록된 변수, 주소 값이 존재하므로 래퍼런스를 통한 참조에 접근 가능
  2. R-Vaule : 메모리에 등록되지 않은 값, 주소 값이 존재하지 않으므로 래퍼런스 사용 불가능
    래퍼런스의 목적은 주소 값에 접근하여 해당 주소가 가리키는 원본 값에 대한 수정
    상수의 목적은 값을 변경하지 못하는 특성을 가지고 있음 -> 리터럴 상수를 래퍼런스로 참조하려는 경우 서로의 특성에 위배한다
    ex)
    // 해당 함수에 매개변수로 받은 int형 iA는 4Byte의 공간을 할당받음
    void First(int iA)
    {
           _iA += 10;
           cout << "First : ";
           cout << _iA << endl;
    }

// 매개변수가 할당받는 공간을 줄이고 싶다면 레퍼런스를 사용할 수 있음 -> 레퍼런스는 메모리 공간을 차지하지 않음
// 하지만 해당 경우 상수를 매개변수로 받을 수 없음


void Second(int& iA)
{
       _iA += 20;
       cout << "Second : ";
       cout << _iA << endl;
}

 

// 상수를 매개변수로 받아오고 싶다면 const 레퍼런스를 사용할 수 있음
// 이제 매개변수로 받을 수는 있지만, 내부에서 해당 상수에 대한 가공이 불가능함


void Third(const int& iA)
{
_iA += 30;
cout << "Third : ";
cout << _iA << endl;
}

 

// R-Value Reference를 사용하면
// 1. 레퍼런스를 사용하여 매개변수에 사용되는 메모리 할당량을 감축시킬 수 있다.
// 2. 상수 타입을 매개변수로 받아올 수 있다.
// 3, 받아온 매개변수에 대해서 가공이 가능하다.


void Forth(int&& iA)
{
        _iA += 40;
       cout << "Forth : ";
       cout << _iA << endl;
}

class CObj
{
private:
       int m_x;
       int m_y;

private:
       CObj() :m_x(0), m_y(0) {}
       CObj(int& _x, int& _y) : m_x(_x), m_y(_y) {}

public:
      static CObj* Make(int&& _X, int&& _Y)
      {
               return new CObj(_X, _Y);
       }
};

 

void main()
{
       First(10);
      Second(20);
      Third(30);
      Forth(40);
      CObj* pPlayer = CObj::Make(10, 20);
}

 

또한 const 래퍼런스를 이용하여 리터럴 상수를 매개변수로 넘겨받은 객체는 해당값을 멤버 변수에 저장하는 행위가 불가능하다
-> R-Value Reference를 사용하여 상수값으로도 쓰기 작업이 가능해진다

 

이동 생성자
복사 생성자 => 원본이 잔존하는 상태로 사본 생성하며, 모든 부분에 대한 복사 진행
이동 생성자 => 자료에 대한 소유권을 아예 넘기므로써 원본이 남지 않고, 시작 주소만 넘김
ex)
class CObj
{
public:
      CObj() : m_pArray(nullptr), m_Size(0) {}
      CObj(int _iSize) : m_pArray(new int[_iSize]), m_Size(_iSize) {}

       // 복사 생성자
      CObj(const CObj& rhs) : m_pArray(new int[rhs.m_Size]), m_Size(rhs.m_Size)
      {
                memcpy(m_pArray, rhs.m_pArray, sizeof(int) * rhs.m_Size);
       }

      // 이동 생성자
      CObj(CObj&& rhs)
      {
               m_pArray = rhs.m_pArray;
               m_Size = rhs.m_Size;
               rhs.m_pArray = nullptr;
       }

       ~CObj()
      {
                if (m_pArray)
               {
                        delete[] m_pArray;
                        m_pArray = nullptr;
               }
       }

private:
       int* m_pArray;
       int m_Size;
};

 

void main()
{
       CObj Origin(1000);                            // int형 데이터 1억 칸을 가지는 배열 생성
       CObj Copy(Origin);                           // 복사 생성
       CObj Move(std::move(Origin));       // 이동 생성

       // std::move -> 매개변수로 전달받은 대상을 R-Value Reference로 캐스팅하는 함수

       vector vec1(1000);
       vec1.push_back(CObj(1000));
       // 1. CObj 임시 객체 생성
       // 2. CObj 생성 동시에 동적 할당 되는 멤버변수 생성
       // 3. 복사 생성자를 호출, 데이터 모두 복사
       // 4. 복사 수행 후 소멸자 호출
       // 5. 임시 객체 멤버 변수 해제 및 소멸

      vector vec2(1000);
      vec2.push_back(std::move(CObj(1000)));
      // 1. CObj 임시 객체 생성
      // 2. CObj 생성 동시에 동적 할당 되는 멤버변수 생성
      // 3. 이동 생성자를 호출, 1번 주소값 이동
      // 4. 복사 수행 후 소멸자 호출
      // 5. 임시 객체 멤버 변수 해제 및 소멸
}