일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 애셋로드
- 정렬
- 자료구조
- 델리게이트
- dataasset
- UE4 커스텀로그
- 람다
- UELOG
- 크리티컬섹션
- 프로그래머스
- UE_LOG
- 약참조
- enumasByue
- 언리얼엔진구조체
- BFS
- 선택정렬
- stl
- UML관련
- 알고리즘
- unorder_map
- 강참조
- moreeffectiveC++
- 스마트포인터
- map
- C++최적화
- 언리얼가비지컬렉터
- 데이터애셋
- C++
- 람다사용정렬
- 정렬알고리즘
- Today
- Total
목록C & CPP/Effective C++ (25)
기억을 위한 기록들
C 스타일의 형 변환(캐스팅)은 이렇다. (T) 표현식 //표현식부분을 T타입으로 형변환합니다. 그리고 함수 방식의 형변환은 문법이 함수 호출 문 같다 T(표현식) //표현식부분을 T타입으로 형변환합니다. 위 2가지는 어떻게 쓰든 구형 스타일의 형 변환이다. 그리고 이제 C++에서의 형 변환은 4가지로 이루어져 있고 새로운 형태의 캐스트 연산자를 독자적으로 제공한다. (신형 스타일 캐스트 혹은 C++ 스타일 캐스트) 1. 객체의 상수 성을 없애는 용도로 사용되는 형 변환. const_cast(표현식) 2. 이른바 '안전한 다운 캐스팅'을 할 때 사용하는 연산자이다. 즉, 주어진 객체가 어떤 클래스 상속 계통에 속한 특정 타입인지 아닌지를 결정하는 작업에 쓰인다. 구현 스타일의 캐스트 문법으로는 흉내 조차..
예를 들어 어떤 함수안에서 어떤 변수를 선언해놓고 그 변수를 사용하기도전에 예기치 못한 상황으로 해당 함수를 탈출하게 되면 해당 변수는 사용해보지도 못하고 선언만 해놓고 다시 소멸하게 된다. void DoSomething() { //.. Player tempPlayer; if(/*어떤 상황으로 인한*/) //if안에 들어가면 tempPlayer는 생성과 소멸만하고 끝난다. return; //탈출 //tempPlayer 객체의 사용 //.. } 이런것들은 위해 최대한 사용하기 직전에 변수를 정의하자. void DoSomething() { //.. if(/*어떤 상황으로 인한*/) return; //탈출 Player tempPlayer; //tempPlayer 객체의 사용 //.. } 그리고 어떤 루프문을 ..
두 객체의 값을 맞바꿔주는 각자의 값을 상대방에 주는 동작이다. 표준 라이브러리의 swap도 아래와 같이 생겼다. namespace std { template void swap(T& a, T& b) { T temp(a); a = b; b = temp; } } 코드를 보면 알겠찌만 복사만 제대로 지원하는 타입이기만 하면 어떤 타입의 객체든 swap이 가능하다. 하지만, 호출 한번에 복사가 3번씩 일어난다 a에서 temp로 b에서 a로 temp에서 b로. 복사를 하게 되면 손해를 보는 타입들 중 으뜸은 아마도 다른 타입의 실제 데이터를 가리키는 포인터가 주성분인 타입일 것이다. 이런 개념을 많이 쓰고 있는 기법이 바로 pimpl 관용구(pointer to implementation)이다. pimpl 설계를 ..
타입 변환이 모든 매개변수에 대해 적용되어야 한다면, 비멤버 함수를 선언하자. 예를 들어 유리수를 나타내는 클래스를 만들고 있다면, 정수에서 유리수로의 암시적 변환을 허용을 판단해도 크게 어이없거나 하진 않을 것이다. C++에서 제공하는 int -> double 변환과 별반 다르지 않다. 유리수를 나타내는 클래스를 하나 보자. class Rational { public: Rational(int num = 0, int denom = 1); //num=분자 / denom=분모 const Rational operator*(const Rational& rhs) const; int GetNum() const; int GetDenom() const; private: //... }; 유리수를 나타내는 클래스이니, o..
어떤 캐릭터 클래스를 만들다가 캐릭터가 죽어서 다시 처음부터 진행되기 위해 상태를 초기화해주는 함수들이 있다고 가정합시다. class Character { public: //.. void SetMaxHP(); //최대체력 만들어주기 void ClearStat(); //능력치 초기화 void ClearInventory(); //인벤토리 비우기..등 //.. }; 이렇게 3개면 기획상의 추가로 점점 많이지게 되면, 한 번에 호출하고 싶게 된다. class Character { public: //.. void SetMaxHP(); void ClearStat(); void ClearInventory(); //.. void ClearEverything() //한번에 호출해주는 함수 추가 { SetMaxHP(); ..
그렇게 생각해야하는 이유 2가지 첫 번째 이유 문법적 일관성이다. 데이터 멤버가 public이 아니라면, 사용자 쪽에서 어떤 객체를 접근할 수 있는 유일한 방법은 멤버 함수일 것이다. 그러면 괄호를 붙어야 하는지 말아야 하는지 고민할 필요가 없어진다.(무조건 괄호) Vector a; a.DoSomething(); //괄호를 붙어야할지 말아야할지 고민할 필요가 없을 것이다. 그리고 멤버변수가 public이면 읽기뿐만 아니라 쓰기 권한을 통제하기 애매해진다. getter/setter 등으로 확실히 하는 게 좋다. 두 번째 이유 캡슐화이다. 함수를 통해서만 데이터 멤버에 접근할 수 있도록 구현해두면, 데이터 멤버를 계산식으로 대체가 가능할 수도 있다. 상황 예) 예를 들면 여러 캐릭터를 조종 할 수 있는 게임..
이전에 작성한 글을 쓰고, 기존의 코드에 멀쩡하게 들어 있는 '값에 의한 전달' 부분을 '참조에 의한 전달'로 무작정 바꾸려고 하면 안 된다. 실제로 있지도 않은 객체의 참조자를 넘길 수도 있기 때문이다. 예를 들어, 어떤 유리수를 나타내는 클래스가 있다고 치자. class Rational{ pulbic: Rational(int num = 0, int denominator = 1); //... private: int n, d; friend const Rational operator*(const Rational* lhs, const Rational* rhs); }; 해당 클래스의 operator*는 곱셉 결과를 값으로 반환하도록 되어있다. 값이 아닌 참조자를 반환할 수 있으면 비용 부담은 확실히 없을 것이..
C++에서 함수로부터 객체를 전달받거나, 함수에 객체를 전달할 때 '값에 의한 전달(Pass By Value)' 방식을 사용한다. 실제 인자의 '사본'을 통해 초기화되며, 어떤 함수를 호출한 쪽은 그 함수가 반환한 값의 '사본'을 돌려받는다. 사본을 만들어내는 원천이 바로 복사 생성자이다. 이 점 때문에 '값에 의한 전달'이 고비용의 연산이 된다. 어떤 클래스 A와 A를 상속받는 클래스 B 두 개가 있다고 할 때, 어떤 함수를 호출하려고 한다. B myB; bool DoSomething(B ref); B 클래스를 값으로 전달받는 함수가 있으면, myB를 매개변수 ref로 초기화시키기 위해 복사 생성자가 호출된다. 게다가 ref는 DoSomething함수가 끝나면서 소멸된다. 이렇게 되면 총 B클래스의 복..
C++에서 새로운 클래스를 정의한다는 것은 새로운 타입 하나를 정의하는 것과 같다. 단순히 클래스 설계를 하는것이 아니라 타입 설계를 한다고 생각하니 더 대단한 것 같다(?). 함수와 연산자를 오버로드하고, 메모리 할당 및 해제를 제어하며, 객체 초기화 및 종료 처리를 정의하는 작업 등 신경 써야 할게 많아지기도 한다는 것이다. 좋은 클래스를 설계한다는 것은 좋은 타입을 설계하는 것이기도 한데, 마냥 쉽기만 하지는 않을 것이다. 1. 문법(syntax)이 자연스럽고, 2. 의미구조(sematics)가 직관적이며, 3. 효율적인 구현이 한 가지 이상 해야 하는데, 고민 없이 클래스 정의를 했다가는 이 중 한가지도 못할 수도 있다. 심지어 멤버 함수조차도 어떻게 선언되었느냐에 따라 수행성능이 달라진다. 고려..
인터페이스는 만리장성을 쌓는 접선 수단입니다. 이상적으로 어떤 인터페이스를 어떻게 써 봤는데 결과 코드가 사용자가 생각한 대로 동작하지 않는다면 그 코드는 컴파일되지 않아야 되는 게 맞다. 반대로 어떤 코드가 컴파일이 되면 사용자가 원하는 대로 동작해야 하는 것이다. '제대로 쓰기엔 쉽고 엉터리로 쓰기에 어려운' 인터페이스를 개발하려면 우선 사용자가 저지를만한 실수의 종류를 미리 생각해봐야 한다. 예를 들어 날짜를 나타내는 어떤 클래스에 넣을 생성자를 설계하고 있다고 가정하자. class Date{ public: Date(int month, int day, int year); //... }; 별 문제가 없을 것 같지만, 매개변수의 순서가 잘못될 여지가 있다. Date d1(21, 4, 2021); //월..
어떤 동적으로 할당한 Player객체에 대해 어떤 처리를 함수는 하나가 있다고 가정하자. void doSomethingPlayer(shared_ptr p,int someValue) { //... } 그리고 어떤 값을 반환하는 함수가 있다고 하자. int GetSomething() { return 5; } 자원 관리 함수를 사용하는 게 좋긴 하니까 스마트 포인터(유니크 포인터)로 인자를 받고 있다. 이렇게 만든 함수를 사용해보려 했으나 int main() { doSomethingPlayer(new Player, GetSomething()); //error! return 0; } 컴파일 에러가 난다. 스마트포인터 생성자는 explicit로 선언되어 있기 때문에 'new Player' 표현식에 의해 만들어진 ..
string* str = new string[100]; //.. delete str; 얼핏 보면 맞는 것 같지만 str이 가리키는 100개의 string 객체들 가운데 99개는 정상적인 소멸을 거치지 못할 가능성이 크다. new 연산자를 사용해서 표현실을 꾸미게 되면(어떤 객체를 동적 할당하면,) 두가지의 내부 동작이 진행된다. 1. 메모리가 할당된다.(operator new 라는 이름의 함수가 쓰인다.) 2. 할당 된 메모리에 대해 한 개 이상의 생성자가 호출된다. 반대로 delete 표현식을 사용하는 경우도 두 가지의 내부 동작이 진행된다. 1. 기존에 할당된 메모리에 대해 한개 이상의 소멸자가 호출된다. 2. 그 후 메모리가 해제된다.(operator delete라는 이름의 함수가 쓰인다.) del..