1. decltype 키워드
decltype 키워드는 인수로 지정한 표현식의 타입을 알아낸다.
int x = 123;
decltype(x) y = 456;
이렇게 작성하면 컴파일러는 y의 타입이 x의 타입인 int라고 추론한다. decltype은 레퍼런스나 const지정자를 삭제하지 않는다는 점에서 auto와 다르다. 템플릿을 사용할 때 상당히 강력한 효과를 발휘한다.
2. 로 스트링 리터럴(raw string liternal)
여러 줄에 걸쳐 작성한 스트링 리터널로서, 그 안에 담긴 인용부호를 이스케이프 스퀀스로 표현할 필요가 없고, \t, \n같은 이스케이프 시퀀스를 일반 텍스트로 취급한다.
문법: R " ( <내용> ) " const char* str = R"(Hello "Pinko")" ;
3. static
(1). static 메서드
static 메서드는 특정 객체에 대해 호출되지 않기 때문에 this 포인터를 가질 수 없으며 어떤 객체의 non-static 멤버에 접근하는 용도로 호출할 수 없다. static 메서드는 근본적으로 일반 함수와 비슷하지만 클래스의 private static이나 protected static 멤버만 접근할 수 있다는 점은 다르다. 또한 같은 타입의 객체를 포인터나 레퍼런스로 전달하는 방법등을 사용해서 그 객체를 static 메서드에서 볼 수 있게 만들었다면 클래스에 non-static private떠는 protected 멤버에 접근할 수 있다.
(2). static 데이터 멤버
클래스의 모든 객체마다 똑같은 변수를 가지는 것은 비효율적이거나 바람직하지 않을 수 있다. static 데이터 멤버는 객체가 아닌 클래스에 속한다. 이는 전역 변수와 비슷하지만 자신이 속한 클래스 범위를 벗어날 수 없다.
선언과 동시에 초기화해도 되지만 일반 변수나 데이터 멤버와 달리 기본적으로 0으로 초기화된다. static 포인터는 nullptr 로 초기화된다. 멤버 공간을 할당하고 값을 초기화하려면 소스파일에 size_t 클래스::변수명 = value;로 초기화 해준다.
(3). static 키워드
- 전역 변수 static : static 키워드가 전역 변수에서 사용될 때 '내부 연결 속성'을 적용한다. 즉, 변수가 정의된 소스 파일 내에서 사용 가능함을 의미한다. (전역 변수는 파일 스코프(or 전역 스코프 :: )를 가진다.)
- 함수 안의 static 변수: 특정 스코프 안에서만 값을 유지하는 로컬(지역) 변수를 만드는 것이다. 함수 안에서 static으로 지정한 변수는 그 함수만 접근할 수 있는 전역 변수와 같다. 어떤 함수에서 초기화 작업 수행 여부를 기억하는 용도로 많이 사용한다.
void incrementAndPrint()
{
static int s_value = 1; // 'static' 키워드를 사용한 '정적 생명 주기', 이 줄은 한번만 실행된다.
++s_value;
std::cout << s_value << '\n';
} // s_value는 여기서 소멸되지 않지만, 접근할 수 없게된다.
출처: https://boycoding.tistory.com/169 [소년코딩:티스토리]
(5). static과 extern 키워드의 내부/ 외부 링크 ★★★★★
링크란? 같은 이름의 여러 식별자가 같은 식별자를 참조하는지를 결정한다. 링크가 없는 변수는 제한된 범위에서만 참조할 수 있다. 지역변수가 링크가 없는 변수의 예이다.
static과 관련된 키워드로는 extern이 있다. const와 typedef는 기본적으로 내부 링크로 처리된다. 여기에 extern을 붙이면 외부 링크가 적용된다. 하지만 extern의 적용 과정은 좀 복잡하다. 어떤 이름을 extern으로 지정하면 컴파일러는 이를 정의가 아닌 선언문으로 취급한다. 변수를 extern으로 지정하면 컴파일러는 그 변수에 대해 메모리를 할당하지 않는다. 따라서 그 변수를 정의하는 문장을 따로 작성해야 한다.
[ static과 extern의 변수 비교 ]
- 내부 링크가 있는 변수를 static 변수라고 한다. static 변수는 변수가 정의된 소스파일 내에서 어디서나 접근할 수 있지만, 소스 파일 외부에서는 참조할 수 없다.
- 외부 링크가 있는 변수는 extern변수라고 한다. extern변수는 정의된 소스 파일과 다른 소스 파일 모두 접근할 수 있다.
extern이 반드시 필요한 경우는 다음과 같이 File.cpp과 같은 다른 소스 파일(외부)에서 x에 접근할게 만들 때 필요하다.
#include<iostream>
extern int x;
int main {
std::cout << x << std::endl;
}
File.cpp에서 x를 extern으로 선언했기 때문에 다른 파일에 있던 x를 여기서 사용할 수 있는 것이다. main( )에서 x를 사용하려면 컴파일러가 x의 선언문을 알아야 한다. x를 선언하는 문장에 extern을 붙이지 않으면 이 문장이 x를 선언하는 것이 아니라 정의한다고 판단해서 메모리를 할당해버린다. 그러면 링크 단계에서 에러가 발생한다. (전역 스코프에 x라는 변수가 두개나 만들어지기 때문이다) 이럴 때는 extern을 붙여서 전역 변수로 만들면 여러 소스 파일에서 공유할 수 있다.
4. const
(1). const 메서드
어떤 메서드가 데이터 멤버를 변경하지 않는다고 보장하고 싶을 때 const 키워드를 붙인다. const는 메서드 프로토타입의 일부분이기 때문에 메서드를 구현하는 코드에서도 반드시 적어야 한다.
객체를 const로 선언하지 않았다면 그 객체의 cosnt 메서드와 non-const 메서드를 모두 호출할 수 있다. 반면 객체를 const로 선언했다면 그 객체의 const 메서드만 호출할 수 있다.
const 객체도 소멸된다. 따라서 언제든지 소멸자가 호출될 수 있다. 그렇다 하더라도 소멸자를 const로 선언할 수는 없다.
(2). constexpr 키워드
c++에서 제공하는 상수 표현식이란 개념이 필요할 때가 있다. 예를 들어 배열을 정의할 때 크기를 상수 표현식으로 지정해야 한다. 상수표현식은 컴파일 시간에 계산된다.
const int getArrSize {return 5;} // 배열 크기 잡기 불가능
constexpr int getArrSize {return 5;} // 배열 크기 잡기 가능
[ 제약사항 ]
- 리턴 타입이 반드시 리터널 타입이어야 한다.
- 클래스의 멤버가 constexpr 함수일 때는 virtual로 선언할 수 없다.
- constexpr 함수의 매개변수는 반드시 리터널 타입이어야 한다.
- dynamic_cast( )와 reinterpret_cast( )를 사용할 수 없다.
- new와 delete도 사용할 수 없다.
5. 인라인(inlining)
(1). 인라인(inlining) 메서드
#define매크로보다 인라인 메스드를 사용하는 것이 더 안전하다. 인라인 메서드를 정의하려면 메서드 정의 코드에서 이름 앞에 inline 키워드를 지정한다. 그러면 컴파일러는 함수 호출하는 부분을 함수 호출로 처리하지 않고, 그 함수의 본문을 곧바로 집어넣는다.
[ 제약사항 ]
인라인 메서드를 호출하는 코드에서 이를 정의하는 코드에 접근할 수 있어야 한다. 컴파일러가 메서드 정의 코드를 볼 수 있어야 메서드 호출 부분을 본문에 나온 코드로 대체할 수 있기 때문이다. 따라서 인라인 메서드는 반드시 프로토타입과 구현 코드를 헤더 파일에 작성한다.
(2). (c++17) 인라인 변수
c++17부터 static 데이터 멤버를 inline으로 선언할 수 있다. 그러면 소스 파일에 공간을 따로 할당하지 않아도 된다.
class 블라블라
{
private:
static inlin size_t 변수명 = 0;
}
클래스에서 이렇게 선언하면 소스 파일에서 size_t 클래스::변수명;를 추가하지 않아도 된다.
6. mutable 데이터 멤버
횟수를 세는 카운터 변수를 mutable(뮤토블)로 선언해서 컴파일러에 이 변수를 const 메서드에서 변경할 수 있다고 알려주면 된다.
7. 명시적으로 오버로딩 제거하기
컴파일러가 자료형에 맞지 않는 값를 넣었을때 경고 메세지만 출력하고 값을 내부적으로 변환해버리는 것을 막기 위해서는 명시적으로 오버로딩을 제거해줘야 한다.
8. 비로컬 변수의 초기화 순서
전역변수와 static 클래스 데이터 멤버는 모두 main( )이 시작하기 전에 초기화된다. 이러한 변수는 소스 파일에 선언된 순서대로 초기화된다. 그런데 c++표준 비로컬(nonlocal:로컬이 아닌) 변수가 여러 소스 파일에 선언됐을 때 초기화하는 순서는 따로 정해두지 않는다. 어떤 소스 파일에 x란 전역 변수가 있고, 다른 파일에는 y라는 전역 변수가 있을 때 어느 것이 먼저 초기화되는지는 알 수 없다.
9. 타입 앨리어스
기존에 선언된 타입에 다른 이름을 붙이는 것이다. 타입을 새로 정의하지 않고 기존 타입 선언에 대한 동의어를 선언하는 문법이라고 할 수 있다. (현업에서 엄청엄청 씀...)
* 함수 포인터에 대한 타입 앨리어스
함수도 내부적으로 특정한 메모리 주소가 지정된다. 물론 함수의 메모리 위치를 신경 쓸 일은 많지 않다. 그런데 C++에서 함수를 데이터 취급할 수 있다. 다시 말해 함수의 주소를 변수다루듯 사용할 수 있다.
함수 포인터의 타입은 매개변수 타입과 리턴 타입에 따라 결정된다. 함수 포인터를 다루는 방법 중 하나는 타입 앨리어스를 사용하는 것이다. 타입 앨리어스를 사용하면 특정한 속성을 가진 함수들을 타입 이름 하나로 부를 수 있다.
using Function = bool(*)(int, int);
void ParmFunction(int a, int b, Function func)
{
bool check = func(a, b);
}
함수를 매개변수로 받는 함수로 작성할 수 있는데 주목할 부분은 Function을 변수처럼 전달하더라도 여전히 일반 함수처럼 호출할 수 있다는 것이다. func(a, b);
10. 스코프 (::)
스코프(유효범위)란 변수, 함수 클래스 이름을 비롯하여 프로그램에서 사용하는 모든 이름은 일정한 스코프에 속한다. 스코프는 네임스페이스, 함수 정의, 중괄호 블록, 클래스 정의 등으로 생성한다. 어떤 변수나 함수, 클래스를 주어진 이름으로 접근할 때 가장 가까운 스코프부터 시작하여 전역 스코프까지 검색한다.
11. 어트리뷰트
어트리뷰트란 특정 벤터에서만 제공하는 정보나 옵션을 소스 코드에 추가하는 메커니즘이다. c++11부터는 속성을 모두 이중 대괄호를 이용하여 [[어트리뷰트]]와 같은 문법으로 표기하도록 표준화됐다. c++ 표준은 여섯 가지 어트리뷰트를 지원한다.
[[ carries_dependency ]] | |
[[ norturn ]] | 함수가 호출한 측으로 제어를 리턴하지 않는다는 것을 의미한다. 주로 프로세스나 스레드를 종료시키거나 익셉션을 던지는 함수에 이 어트리뷰트를 지정한다. |
[[ deprecated ]] | 더 이상 지원하지 않는 대상을 지정할 때 사용한다. 현재 사용할수는 있지만 권장하지 않는 기능임을 표시한다. 지원 중단 사유를 인수로 지정할 수 있다. [[ deprecated ("Unsafe method") ]] void function( ) ; |
(c++17) [[ fallthrough ]] | switch문에서 의도적으로 폴스루를 적용하고 싶을 때 사용한다. |
(c++17) [[ nodiscard ]] | 값을 리턴하도록 정의된 함수에 [[nodiscard]]를 지정하면 그 함수를 이용하는 코드에서 리턴 값을 사용하지 않을 때 경고 메시지가 발생한다. |
(c++17) [[ maybe_unused ]] | 프로그램에서 사용하지 않는 코드를 발견해도 경고 메시지를 출력하지 말라고 컴파일러에 지시할 때 사용한다. |
12. 레퍼런스
- 레퍼런스 변수는 반드시 생성하자마자 초기화해야 한다. (레퍼런스 변수를 클래스 밖에서 선언만 하고 초기화하지 않으면 컴파일러 에러가 발생한다.)
- 정수 리터널처럼 이름 없는 값에 대해서는 레퍼런스를 생성할 수 없다. (But, const 값에 대해서는 레퍼런스를 생성 할 수있다.)
[ 이유 ]
상수가 non-const레퍼런스이기 때문이다. unnamedRef1은 5라는 상수를 수정하겠다는 뜻이기 때문에 성립하지 않고, 반면 unnamedRef2는 const 레퍼런스로 선언했기 때문에 값을 변경할 일이 없기 때문에 문제없이 컴파일이 된다.
- 임시 객체에 대해 non-const 레퍼런스는 만들 수 없지만 const 레퍼런스는 얼마든지 만들 수 있다.
(1). 레퍼런스 대상 변경하기
레퍼런스는 처음 초기화할 때 지정한 변수만 가리킨다. 레퍼런스는 한 번 생성되고 나면 가리키는 대상을 바꿀 수 없다.
(2). 레퍼런스는 모든 타입에 대해서 만들 수 있다.
int*& ptrRef = intP; //ptrRef는 intP에 대한 래퍼런스이고, intP는 int에 대한 포인터이다. ptrRef를 수정하면 intP가 바뀐다. 레퍼런스가 가져온 주소는 그 레퍼런스가 가리키는 변수의 주소와 같다. (레퍼런스의 주소는 값에 대한 포인터와 같다.)
(3) 포인터와 레퍼런스 사용 ★★★
- 가리키는 위치를 변경해야 하는 경우가 있다면 포인터를 사용해야 한다.
- 주소값이 nullptr이 될 수 있는 optional 타입은 반드시 포인터를 사용해야 한다.
- 컨네이터에 다형성 타입을 저장할 때도 포인터를 사용해야 한다.
- 매개변수나 리턴값은 소유권이 어디에 있는지 따져봐야 한다. 메모리의 소유권이 변수를 받는 코드에 있으면 객체에 대한 메모리 해제하는 책임은 그 코드에 있으므로 포인터를 사용해야 한다. 기왕이면 스마트 포인터 ! 그러나 소유권을 이전할 필요가 없다면 레퍼런스로 전달하면 좋다.
Reference.
(도서) 전문가를 위한 C++ (개정4판)
- 11장 C++의 까다롭고 유별난 부분 정리
'👨🏻💻 programming > ◽ c, c++' 카테고리의 다른 글
(c++) 인코딩(Encoding),유니코드(Unicode), 로케일, 패싯 (0) | 2022.09.07 |
---|---|
(c++) 디자인 패턴 01(싱글톤, 추상 팩토리, 옵저버, 프록시, 어댑터) (2) | 2022.08.24 |
(c++17) 전처리 지시자, if-switch 이니셜라이저, __func__, 구조적 바인드 (0) | 2022.08.04 |
[C++] 멀티 프로그래밍(전문가를 위한 C++ , Chapter 27 정리 ) (0) | 2022.05.16 |
[c++]'전문가를 위한 C++17'을 공부하며 정리(ing...) (0) | 2022.05.16 |
안 하는 것 보다 낫겠지
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!