Professional C++ - C++ 고급 기능 (?) (Ch1, Part 2)

1.2.2 C++ String

 

1.2.2 Pointer + Dynamic memory allocation

  • Stack
    • 함수마다 독립적인 메모리 공간 제공
    • 함수가 끝나면 자동으로 메모리 할당 해제
  • Heap
    • Stack과 독립적인 메모리 공간
    • 함수가 끝나도 계속 저장하고 싶으면 Heap에 저장
    • 스마트 포인터가 아닌 이상 자동으로 할당 해제 불가능
  • Raw pointer
    • C에서 사용하는 malloc/free 대신 new/delete 사용.
    • 개인적으로 코딩테스트용 아니면 잘 안쓰는 편…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 기본 구조
int* intPointer;
int* intPointer2 = nullptr;
auto intPointer = new int;

// 동적 배열 할당
int arraySize = 10;
int* myVariableSizedARray = new int[ArraySize];

// 참조 + 역참조
int i = 8;
int* intPointer = &i;
*intPointer = 8;

// 구조체 리턴
Employee* employee = getEmployee();
std::cout << (*employee).salary << std::endl;

Employee* employee = getEmployee();
std::cout << employee->salary << std::endl; // 이 방식이 좀 더 읽기 쉬운듯

  • Smart pointer
    • std::unique_ptr
      • scope를 벗어나거나 삭제되면 메모리 자동 해제
        • return, 또는 exception에서 이득
    • std::shared_ptr
      • Reference counter가 0이 되면 메모리 해제
1
2
3
4
5
6
7
8
9
// 기본
auto employee_unique = std::make_unique<Employee>(); // unique_ptr
auto employee_shared = std::make_shared<Employee>();

// 배열
auto employees = std::make_unique<Employee[]>(10);

// 구조체 멤버 변수 호출
std::cout << employee->salary << std::endl; // raw pointer랑 동일

1.2.3 const

  • C에서는 상수 값을 #define으로 사용했었음.
  • C++에서는 const를 사용함.
    • #define은 보통 하드웨어 또는 빌드 configuration이 아닌 이상 잘 안씀.
  • 값이 변경되지 않도록 보장하는 작업.
    • 실수로 변경하는 경우만 보호해줌.
  • 함수 매개변수로 const를 사용해서, 함수가 실행되는 도중 정보가 의도치 않게 변경되는 것으로부터 보호함.
1
2
3
4
5
6
7
const double kPi = 3.141592654;
const std::string path = "../../file.exe";

void foo(const std::string& someString)
{
someString = "New string!"; // 여기서 컴파일 에러 발생
}

 

1.2.4 레퍼런스

  • 메모리를 새로 할당하지 않고 기존 변수에 새 이름을 지정할 수 있음.
    • int x = 100; int& refVariable = x;
  • Pass by reference‘는 함수를 만들 때 고려해야하는 중요한 포인트임.
    • 일반적으로 함수에 변수를 전달할 때는 pass by value를 사용함.
      • 이 방식은 기존 변수를 copy한 후에 함수에 전달 함.
        • 원본이 손상되지 않음
      • 즉, copy에 대한 계산이 필요없어짐.
      • 또 원본을 수정할 수 있음.
  • 종종 큰 구조체를 넘기는 경우 1. 원본을 수정하고 싶지 않으면서, 2. 복사에 시간이 너무 오래걸리는 경우가 있음.
    • 그럴 때는 ‘Pass by const reference‘를 사용하면 됨.
    • 복사를 하지 않고 reference를 넘기면서, const로 보호처리를 하는 것임.
    • container의 형태를 하고 있는 것들은 왠만하면 다 reference를 걸고 넘기는 것이 좋음.
      • primitives는 그냥 넘겨도 무방함.
      • 이미지 같이 큰 구조체를 넘긴다? 백프로 reference걸고 넘겨야함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void passByValue(int i, int j, int k)
{
... // i, j, k의 복사본이 들어옴.
}

void passByReference(std::string& inputString)
{
inputString = "String updated"; // 원본 inputString의 값이 업데이트 됨
}

void passByConstReference(const std::string& inputString)
{
inputString = "String updated?"; // 컴파일러 에러
}

 

1.2.5 Exception

  • C++는 잘못된 메모리에 접근해도 에러를 내지 않는다.
  • 이런 의도치 않은 실수를 예방하기 위해 exception을 사용한다
    • Exception을 사용해서 프로그램이 터질 때 버그리포트를 만들게 할 수 있다.
  • throw, 또는 try & catch가 많이 사용된다.
    • 하지만 이걸 쓰지 않고 assert로 통일해서 쓰는 회사들도 있다.
    • assert는 debug 모드일 때 버그를 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void processImage(const Image& img)
{
if (img.empty())
throw std::invalid_arguemnt("Input image is empty. Cannot process empty images");

... // some image processing
}

try
{
std::cout << foo(0,0) << std::endl;
std::cout << foo(10,0) << std::endl;
std::cout << foo(100,0) << std::endl;
}
catch (const std::invalid_argument& exception)
{
std::cout << "Exception" << exception.what() << std::endl;
}

 

1.2.6 Type inferencing

  • auto 키워드
    • 어디서 쓰나?
      • 자동으로 함수의 리턴 타입 추론
      • Structural binding
      • lambda 함수 등에서 쓰임
    • 되도록이면 auto 쓰기!
      • 처음에 type이 어떤 형태인지 명시적으로 표현할 때만 제대로 표현해주고, 그 후에는 변수 이름을 적당히 만들어줘서 모두 auto로 돌리는게 좋다.
      • 개인적으로 함수를 만들 때 리턴 타입에는 auto를 안쓰는 편.
      • std::make_unique<>()std::make_shared<>()를 쓸 때 좌변에는 무조건 auto를 쓰는 편. 왜냐하면 오른쪽에 어차피 타입이 적혀있기 때문.
        • 가독성 부스트
  • decltype 키워드
    • 인수로 지정한 표현식의 타입을 알아냄.
    • const를 삭제하지 않음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
std::string foo()
{
std::string str = "some string";
return str;
}

const std::string constFoo()
{
const std::string str = "some const string";
return str;
}

int main()
{
// auto 쓰기
auto str1 = foo();
const auto str2 = constFoo(); // 여기서 const 중요!! const를 return하는데 그냥 auto만 써버리면 copy가 일어남.

// decltype
int x = 123;
decltype(x) y = 456; // x의 타입 가져오기

decltype(constFoo()) str3 = constFoo(); // const std::string 리턴 타입을 전부 추론함.
}