[필요한것만 골라 배우는 모던 C++] 1부

hyng·2023년 5월 3일
1

필요한것만 골라 배우는 모던 C++

{} 초기화 방식을 사용해야 하는 이유

  • 값을 온전하게 담을 수 있는지 확인한 후에 초기화한다.
    • long l{1234567890123} ← 우변의 값을 잃게 된다.

c스타일의 문자열 표현 방법

  • char[8] cstring = …;
    • c스타일의 문자열은 문자열 가장 뒤에 ‘\0’ 문자를 넣어주어야 하며, 문자열을 읽을 때 ‘\0’가 나올 때까지 문자열을 읽는다. 이는 메모리를 침범하는 문제를 일으킬 수 있다.
    • 그리고 문자열을 덧붙이는 경우에도 주의를 기울여야 하기 때문에 상수 문자열을 사용할 경우에만 c스타일의 문자열을 사용하고 (const char[8] = “…” ) 그 외에는 c++ string 을 사용하는 것이 낫다.
    • 문자열 리터럴을 사용할 경우에 이는 c스타일의 문자열로 사용된다.
      • “This is literal” // char str[16] = “This is literal”;
    • string은 동적 할당을 사용하고 문자열의 길이가 일정 길이 이하라면 변수 자체에 값을 저장하는 방식으로 성능을 최적화 한다.

한 줄의 코드에 서로 다른 여러 관심사가 포함되지 않도록 해야 한다

배정문을 작성할 때는, 배정문에서 변경되는 것은 배정 연산자의 왼쪽에 있는 변수뿐 이어야 한다는 원칙을 지키는 것이 바람직하다.

  • 우변의 표현식에 부수 효과가 없으면 사람이 프로그램의 행동을 이해하기 쉬울 뿐만 아니라 컴파일러가 코드를 최적화하는 데에도 도움이 된다.

인라인화

  • 함수 호출은 많은 비용이 드는 작업이다. 레지스터들을 저장하고 인수들을 스택에 복사하는 등의 사전작업이 필요하다.
  • 이런 오버로드를 피하기 위해 컴파일러는 함수 호출을 인라인화 한다.
  • 인라인화는 함수 호출을 함수에 담긴 연산들로 대체하는 것이다.

에러 처리 방법

  • 단언(assertion), 예외(exception)
  • static_assert
    • 프로그램의 오류를 컴파일 시점에 미리 검출할 수 있다.

배열

  • 배열의 문제점
    • 배열 접근 시 색인의 유효성이 점검되지 않는다. → 현대적 c++에 도입된 array 형식으로 해결
    • 배열의 크기를 반드시 컴파일 시점에 알아야 한다. → 동적 메모리 할당으로 해결
      • 예를 들어 파일에서 데이터를 읽어서 배열을 채울 때 심각한 제약이 된다.
        ifstream ifs("some_array.dat");
        ifs >> size;
        float v[size]; //오류: size는 컴파일 시점 상수가 아님 
  • 초기화되지 않은 포인터는 무작위 한(임의의 비트 패턴) 값을 가진다.
  • 초기화 방법
    // good
    int * test = nullptr;
    int * test{};
    
    //bad
    int * test = 0;
    int * test = NULL; 
    • 0 이라는 주소는 특별하다. 응용 프로그램이 이 주소를 사용하는 경우는 없으므로, 이 주소는 포인터가 아무것도 가리키지 않음을 나타내는 용도로 적합하다.
    • 그렇지만 수치 리터럴 0 자체에 그러한 용도가 내장되어 있는 것은 아니라서 함수 중복적재 해소 시 중의성을 발생할 수 있다. NULL을 사용해도 마찬가지이다. 이 문제를 해결하기 위해 c++11은 0에 해당하는 포인터 리터럴 nullptr를 도입했다. nullptr는 포인터가 아닌 형식과 혼동할 여지가 없고 “아무것도 가리키지 않는 포인터” 라는 뜻을 좀 더 명확하게 나타낸다는 점에서, 0이나 NULL 대신 이것을 사용하는 것이 바람직하다.

스마트 포인터

  • unique_ptr
    • 객체에 대한 유일 소유권을 나타낸다.
    • 범위를 벗어날 때 해당 메모리가 자동으로 해제된다.
      • 따라서 동적으로 할당하지 않은 주소로 초기화해 서는 안 된다.
    • 임시 객체에는 이동 의미론이 적용되기 때문에, 함수가 unique_ptr을 반환할 수 있다.
    • 배열에 대해 특수화가 되어 있어, 내부적으로 delete[]를 수행하고 보통의 배열과 같은 방식으로 요소들에 접근하는 수단도 제공한다. 대신 배열 unique_ptr에는 *를 사용할 수 없다.
  • shared_ptr
    • 하나의 객체에 대해 여러 포인터가 공유한다.

    • 가능하다면 new를 사용하지 말고 make_shared 함수로 shard_ptr를 생성하는 것이 바람직하다.

      • new를 사용한 경우
      • make_sharded를 사용한 경우
        - 포인터 자체의 내부 관리 데이터와 사용자 데이터가 하나의 메모리 블록에 함께 할당되었음, 이렇게 하면 메모리 캐시 효율성이 좋아진다. 또한, make_shared는 메모리 블록을 한 번만 할당하므로 예외 안전성이 좋다.

메모리를 동적을 관리하려면 포인터를 사용할 수밖에 없다. 그냥 다른 객체를 가리키는 또는 “참조하는” 것이 목적이라면 포인터 대신 참조를 사용하면 된다.

#include 지시문을 만난 전처리기는 표준 헤더 디렉터리들(유닉스류 시스템에서는 /usr/include와 /usr/local/include)에서 해당 헤더 파일을 찾는다.

한 번 포함된 헤더는 다시 포함되지 않게 하는 기법 → 포함 가드

#ifdef HERBERTS_MATH_FUNCTIONS_INCLUDE //해당 매크로 상수가 선언 되어있으면 헤더 포함

#define HERBERTS_MATH_FUNCTINOS_INCLUDE

#endif

하지만 이 방법은 프로젝트 전체에서 매크로 상수가 중복되면 안된다는 문제점이 있다.

대안으로 #pragma once가 있다.

public 멤버 변수와 public 메서드 인터페이스는 클래스의 인터페이스를 규정한다.

profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글