일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 복사생성자
- 생성자
- 포인터
- UE5
- 자료구조
- AI
- 배열
- 게임개발
- Unreal
- 언리얼5
- 언리얼 엔진5
- 언리얼엔진
- C언어
- 게임프로그래밍패턴
- BehaviorTree
- 복사대입연산자
- 언리얼
- 프로세스
- 게임 개발
- 소멸자
- 언리얼엔진5
- 프로그래밍
- CPP
- effectivec++
- cpp개발
- Unreal Engine
- Unreal Engine5
- C++
- 디자인패턴
- 데이터구조
Archives
- Today
- Total
리얼 개발
[Effective C++] 공부 정리 2 본문
1. C++에 왔으면 C++의 법을 따릅시다.
항목1 : C++를 언어들의 연합체로 바라보는 안목은 필수
C++은 다음 4가지 하위 언어들의 연합체이다.
- C언어
- C++은 여전히 C를 기본으로 하고 있다.
- 블록, 문장, 선행 처리자, 기본제공 데이터 타입, 배열, 포인터 등 모든 것이 C에서 왔다.
- 객체 지향 개념의 C++
- 클래스를 쓰는 C
- 클래스, 캡슐화, 상속, 다형성, 가상 함수 등
- 템플릿 C++
- C++의 일반화 프로그래밍
- 새로운 프로그래밍 패러다임인 템플릿 메타프로그래밍(template metaprograming : TMP)이 파생
- STL
- Standard Template Library의 줄임말
- 컨테이너, 반복자, 알고리즘, 함수 객체
(*) C++을 사용한 효과적인 프로그래밍 규칙은 경우에 따라 달라진다. 그 경우란, 바로 C++의 어떤 부분을 사용하느냐이다.
요약
C++은 오랫동안 발전되며 다양한 기능을 제공하는 강력한 툴이 되었다. 그렇기에 배움에 있어 진입장벽이 높지만 C++을 하위 4개의 언어로 쪼개서 생각할 수 있다면 C++ 이해의 관문에 들어가기 쉬울 것이다.
항목 2 : #define을 쓰려거든 const, enum, inline을 떠올리자.
가급적 선행 처리자보다 컴파일러를 더 가까이하자.
- 매크로의 경우 컴파일러가 쓰는 기호 테이블에 들어가지 않는다.
- #define ASPECT_RATIO 1.653
- 해당 코드가 들어간 부분에서 컴파일 에러가 발생하면 에러 메세지에는 1.653이 나온다.
- 매크로 대신 상수를 사용하자.
- 매크로 함수 대신 인라인 함수에 대한 템플릿을 사용하자.
#define ASPECT_RATIO 1.653
const double AspectRatio = 1.653;
class GamePlayer
{
private:
static const int NumTurns = 5; //상수 선언, 정의된 것이 아니다.
int scores[NumTurns];
};
//클래스 상수의 정의는 구현파일에 둔다.
//정의에는 상수의 초기값이 있으면 안된다.
//왜냐하면 클래스 상수의 초기값은 해당 상수가 선언된 시점에서 바로 주어지기 때문이다.
const int GamePlayer::NumTurns;
//정수 타입의 정적 클래스 상수에 대한 클래스 내 초기화를 금지하는 구식 컴파일러의 경우
class GamePlayer2
{
private:
enum { NumTurns = 5 };
int scores[NumTurns];
};
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
//매크로 함수 대신 인라인 함수를 우선 생각하자.
template<typename T> //T가 정확히 무엇인지 모르기 때문에, 매개변수로 상수 객체에 대한 참조자를 쓴다.
inline void CallWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
void f(int Value)
{
std::cout << Value << std::endl;
}
int main()
{
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); //a가 두번 증가하는 괴현상, 인자를 여러번 평가한다.
return 0;
}
(*) 단순한 상수를 쓸 때는, #define 보다 const 객체 혹은 enum을 우선 생각하자.
(*) 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각하자.
요약
const, enum, inline의 존재를 늘 유념하고 #define을 될 수 있으면 피하자.
항목 3 : 낌새만 보이면 const를 들이대 보자!
- const는 ‘의미적인 제약’ 을 소스 코드 수준에서 붙인다는 점과 어떤 객체의 내용이 불변이어야 한다는 제작자의 의도를 컴파일러 및 다른 프로그래머와 나눌 수 있는 수단이라고 할 수 있다.
- 포인터
char str[] = "Hello";
char* p = greeting; //비상수 포인터
//비상수 데이터
const char* p = greeting; //비상수 포인터
//상수 데이터
char* const p = greeting; //상수 포인터
//비상수 데이터
const char* const p = greeting; //상수 포인터
//상수 데이터
- 반복자
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();
//iter는 T* const 처럼 동작한다.
*iter = 10; //OK, iter가 가리키는 대상을 변경한다.
++iter; //에러! iter 은 상수이다.
std::vector<int>::const_iterator cIter = vec.begin();
//cIter는 const T* 처럼 동작한다.
*cIter = 10; //에러! *cIter가 상수이기 때문에 안된다.
++cIter; //OK, cIter을 변경하는건 가능하다.
- 상수 멤버 함수
- 멤버 함수에 붙는 const 는 “해당 멤버 함수가 상수 객체에 대해 호출될 함수이다” 라는 사실을 알려준다. (?)
//멤버 함수의 경우 반환값, 매개변수, 함수 자체에 const를 붙일 수 있다.
const T func(const paramiter) const
//const 키워드가 있고 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다.
Test operator*(const Test& test)
{
return Test(a * test.a, b * test.b);
}
const Test& operator*(const Test& test) const
{
return *this;
}
- 컴파일러 쪽에서 보면 비트수준 상수성을 지켜야 하지만, 우리는 개념적인 상수성을 사용해서 프로그래밍 하면 된다.
- 비트수준 상수성
- 어떤 멤버 함수가 그 객체의 어던 데이터 멤버도 건드리지 않아야한다.
- 객체를 구성하는 비트들 중 어떤 것도 바꾸면 안된다.
- 논리적 상수성
- 상수 멤버 함수도 객체의 일부 몇 비트를 바꿀 수 있되 사용자 측에서 알아채지 못하도록(객체의 상태에 영향을 주지 않도록) 해야 한다.
- mutable 키워드 사용, mutable은 비정적 데이터 멤버를 비트수준 상수성의 족쇄에서 풀어준다.
- 비트수준 상수성
- 상수 멤버 및 비상수 멤버 함수에서 코드 중복 현상을 피하는 방법
class TextBlock
{
public:
const char& operator[](std::size_t position) const
{
//작업들
return text[position];
}
char& operator[](std::size_t position) const
{
//operator[]의 반환 타입에 캐스팅을 적용, cosnt를 떼어낸다.
//*this의 타입에 const를 붙이고 operator[]의 상수 버전을 호출한다.
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
}
//반대로 상수 멤버 함수에서 비상수 멤버 함수를 호출하는 일은 할 수 없다.
요약
const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 준다. 웬만하면 const를 아낌없이 붙이자.
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자.
- 기본제공 타입으로 만들어진 비멤버 객체에 대해서는 초기화를 손수 해야한다. 이 부분을 제외하면 C++ 초기화의 나머지 부분은 생성자로 귀결된다.
- 대입과 초기화를 헷갈리지 않는 것이 중요하다.
- 객체의 데이터 멤버는 생성자의 본문이 실행되기 전에 초기화되어야 한다.
- 기본제공 타입의 경우에는 생성자에서 대입되기 전에 초기화되리란 보장이 없다.
class ABEntry
{
public:
ABEntry(const std::string name, const std::string& address, const std::list<PhonNumber>& phones);
std::string GetName() const { return theName; }
int GetInt() const { return numTimesConsulted; }
private:
std::string theName;
std::string theAddress;
std::list<PhonNumber> thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string name, const std::string& address, const std::list<PhonNumber>& phones)
{
theName = name; //지금은 모두 '대입'을 하고 있다.
theAddress = address; //'초기화'가 아니다.
thePhones = phones;
numTimesConsulted = 0;
//theName, theAddress, thePhones의 경우 기본 생성자를 호출해서 초기화를 미리 해 놓은 뒤 곧바로 새로운 값을
//대입하고 있다.
}
ABEntry::ABEntry(const std::string name, const std::string& address, const std::list<PhonNumber>& phones)
:theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) //초기화
//복사 생성자에 의해 초기화되고 끝난다.
{ }
- 정적 객체(static object) : 자신이 생성된 시점부터 프로그램이 끝날 때까지 살아 있는 객체, 다시 말해 main() 함수의 실행이 끝날 때 정적 객체의 소멸자가 호출된다.
- 지역 정적 객체(local static object)
- 함수 안에서 static으로 선언된 객체
- 비지역 정적 객체(non-local static object)
- 전역 객체
- 네임스페이스 유효범위에서 선언된 객체
- 클래스 안에서 static으로 선언된 객체
- 파일 유효범위에서 static으로 선언된 객체
- 지역 정적 객체(local static object)
요약
데이터는 직접 초기화해주는게 웬만한 상황에서 좋다. 멤버 초기화 리스트를 사용하는 습관을 들이자.
'C++ > 효과적인 C++' 카테고리의 다른 글
[Effective C++] 공부 정리 5 (0) | 2024.10.25 |
---|---|
[Effective C++] 공부 정리 4 (0) | 2024.08.20 |
[Effective C++] 공부 정리 3 (0) | 2024.07.13 |
[Effective C++] 공부 정리 1 (1) | 2024.07.13 |