-
전문가를 위한 C++ 정리(1)@ 16. 1 ~ 17. 1/C++ 2015. 3. 28. 18:40
책을 정독하면서 까먹거나 자주 잊는것에 대해 정리..쉽게 아주 쉽게..
1. auto 자료형
auto형은 컴파일러에 의해 자동으로 타입이 정해지는 타입
2. enum class
enum class 이름
{
}
이런식으로 사용을 하는데..class와 같다 그런데 기존 enum과 다른점은 엄격한 상태라 일반 정수타입변수로 캐스팅이 안되고 비교도 안된다.
3. 구간 지정 for루프
대상 : 배열과 반복자를 리턴하는 begin() end()함수를 멤버로 가진 데이터 타입(STL 컨테이너)
for(auto 변수명 : 배열 또는 STL)
{
}
예) for(auto &i : arr)
4. std::array
vector와는 달리 크기가 고정되어 있어 새로 항목 추가, 삭제할수 없음.(그런데 오버해드가 적음)
STL 반복자를 활용하여 STL알고리즘도 이용가능한것이 장점
array<자료형, 크기> 변수명
5. unique_ptr, shared_ptr 차이는
- shared_ptr 클래스는 공동 소유권(shared ownership)이라는 개념을 구현한다.여러 포인터가 동일한 객체를 참조할 수 있기 때문에 객체와 그 객체가 가진 자원은
모든 포인터가 소멸될 때 (마지막 포인터가 소멸될 때) 같이 소멸된다.
- unique_ptr 클래스는 베타 소유권(exclusive ownership)혹은 엄격한 소유권(strict ownership) 이라는 개념을 구현한 포인터다.
이 포인터는 해당 객체를 참조하는 스마트 포인터는 오직 하나만 존재하는 것을 보장한다.
대신 소유권을 전달하는 것은 가능하다.
이 포인터는 객체를 new로 생성한 후 예외가 발생해 delete를 호출하지 못하는 상황에서 발생하는 메모리 누수를 막는데 유용하다
6. 변수로부터 포인터를 얻을 때는 주소 참조 연산자 &를 이용함.
int i=8;
int *ptr=&i;
7. this 포인터를 다른 함수나 다른 클래스의 메서드를 호출할 때 파라미터로 활용할 수도 있다.
예를 들어 printcell(A* a)이라는 독립함수가 있고
A클래스내 A::set() 함수가 있다고하면 set()함수내에서 printcell(this)로 this를 A* a에 대응하는 파라미터로 사용가능함.
8. 스마트 포인터를 사용하지 않으면 객체를 삭제한 후 nullptr로 대입함.
9. 이름은 같지만 파라미터의 개수나 타입이 다르다면 오버로딩
상속을 받으면서 슈퍼클래스에서 이미 정의된 메서드의 행동을 바꾸는 것은 오버라이딩
(파라미터에 차이없이 리턴 타입만 다른 메서드나 함수에 대해서 오버로딩을 허용하진 않음..const여부는 허용됨)
10. 생성자를 여러개 정의하다보면 생성자 안에서 다른 생성자를 이용하고 싶을때가 있지만 안된다.(기대한 대로 동작이 안됨)
C++11의 위임된 생성자 방법으로는 됨..(기대하는 대로)
11. 객체의 배열을 생성하기 위해선 디폴트 생성자가 반드시 필요하다. 없다면 안된다.
특히 객체 저장에 vector와 같은 stl 컨테이너를 이용하려면 디폴트 생성자는 반드시 필요함.
12. 이전 c++에서는 명시적으로 파라미터가 존재하는 생성자를 사용해야할때 파라미터가 없는 디폴트 생성자도 무조건 정의해 주어야했다.(복사 생성자도 마찬가지임!!!)
아래처럼
class A
{
public:A(){}
A(); //인터페이스라면 구현부를 노출하면 안되니 이렇게하고..cpp파일에 구현부를 작성
A(int i);
};
C++11 에서는 명시적인 디폴트 생성자라는 개념이 생김.
class A
{
public:A()=deault; //반대로 =delete라고 하면 자동으로 생성하는 디폴트 생성자를 포함하여 생성자가 전혀 없는 클래스를 만들수 있음
A(int i);
};
13. 데이터 멤버 클래스가(데이터 멤버로 클래스를 가졌을떄..) 디폴트 생성자가 없다면 반드시 생성자 초기화 리스트를 이용하여 그 멤버 클래스의 생성자를 호출해야함.
물론 일부 생성자 바디에서 값 대입으로 멤버 초기화가 가능하지만..초기화 리스트로만 가능한게 있다.
const, &(참조), 방금위의 설명(데이터 멤버 클래스..기본x), 디폴트가 없는 기본클래스(상속개념에서)
14. c++11 initializer_list 생성자
이것은 첫번쨰 파라미터로 initializer_list<T> 타입을 넘겨받으면서 다른 파라미터가 없거나 디폴트 값이 지정된 파라미터만 가지는 생성자를 이야기함.
사용하기위해선 <initializer_list>를 인클루드 해야함.
class PointSe
{
PointSe(initializer_list<double> args) // true
PointSe(initializer_list<double> args, int i) // false
PointSe(initializer_list<double> args, int i=0) // true
}
이 생성자 안에서는 반복자를 이용하여 args로 넘어온 값들에 접근할 수 있다.
STL이 모두 지원하고 있음..그래서 초기화하는데 STL 포함해서
PointSe p1 = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 };
vector<string> myVec={"String1", String2"};
으로 할 수 있는것이다..{ }이거 잘보라구..
15. 대입연산자는 복제 생성자처럼 원본 객체를 const참조 타입으로 받는다.
SpreadsheetCell& operator=(const SpreadsheetCell& rhs); //대입연산자
SpreadsheetCell(const SpreadsheetCell& rhs); //복제 생성자
여기서 대입연산자의 반환값이 참조객체를 리턴하는 이유는 연산이 중첩될수 있기 떄문에
객체1 = 객체2 = 객체3 (그냥 오버해드 피하려고 하는게 정답이다..)
16. 데이터 멤버 중 객체가 있으면 컴파일러가 자동으로 생성하는 복제 생성자에서는 멤버들의 복제 생성자를 재귀적으로 호출한다.
(그러니까 임의로 복제생성자를 안만들면 자동으로 데이터 멤버중 객체는 자동으로 복제생성자를 호출해준다는건데..)
직접 복제 생성자를 만들 떄는 컴파일러가 생성하는 디폴트 복제 생성자와 마찬가지로 생성자 초기화 리스트에서 멤버의 복제 생성자를 호출할 수 있다.
(직접 복제생성자를 만들었을때 데이터 멤버 중 객체가 있으면 초기화 리스트에서 멤버의 복제 생성자를 호출할 수 있다는 것..그냥 안만들게 하는편히 편하긴 한데..보통 클래스 안에서 동적 메모리 할당을 사용할때 복제 생성자와 대입연산자를 정의하니..)
그런데 만약 멤버 초기화를 통한 멤버의 복제 생성자 호출을 빼먹으면(복제 생성자를 직접만들고 ! 데이터 멤버 중 객체를 초기화 리스트에서 호출 안하면!) 컴파일러가 빠뜨린 멤버에 대해서 디폴트 생성자를 부른다. 이 디폴트 생성자는 직접 작성한 복제 생성자의 바디가 실행되기전 수행된다.
갑자기 왠 디폴트 생성자냐고? 그건 나도 모름..
또한 상속에서도 이런문제가 발생되는데..
(복제 생성자와 대입연산자가 사용된 클래스의 서브클래스를 만들때는 주의해야함..)
만약 서브클래스에 포인터 같은 특별한 데이터가 없어서 복제 생성자나 대입 연산자에서 해줘야 할일이 없다면
서브클래스에서 다시 구현할 필요가 없다.(그냥 상속해도된다고)
그런데..포인터 같은 특별한 데이터가 서브클래스에 있다면(동적 할당 등..)
그러면 서브클래스에서도 복제 생성자를 명시적으로 정의해야하는데 이때 반드시 부모의 복제 생성자를 호출해야한다.(초기화 리스트로~~)
만약 호출하지 않으면 부모클래스의 데이터에 대해서는 디폴트 생성자가 사용된다.(디폴트 복제 생성자가 아님)
대입연산자도 마찬가지인데..호출할때는
부모::operator=(대입연산자의& 자식매개변수);
이런식으로 몸체에서 실행하면됨..
17. 얕은복제의 문제점은 동적할당받은 메모리를 가지고 있을때 발생한다.(즉, 주소값만 복제하는 상황)
그래서 클래스에서 메모리를 동적으로 할당하는 경우가 있따면 항상 복제와 대입연산자를 프로그래머가 직접구현해야함.
그런데 중요한점 하나는 복제생성자에서는 대입받는 객체가 생성되기전이므로 깊은복사전 메모리 해제가 필요없는데 대입연산자에서는 이미 객체가 생성되어 있는 상태이므로 대입받는 객체가 메모리를 먼저 해야한다.(안하면 당연히 대입받는것에 대한 메모리 릭 발생!)
17-1 대입연산과 값에 의한 전달 막기
복제 생성자와 대입연산자를 private:로 설정하면 클래스에서 동적메모리 할당시 문제를 막을수 있다.
private:
복제 생성자(const 복제생성자 &rhs);
대입연산자& operator=(const 대입연산자 &rhs);
이렇게 하면 객체를 함수에서 리턴하거나 인자로 사용하면서 값으로서 전달하려 하면 컴파일 에러가 된다.
하지만 포인터나 &으로 전달시에는 가능!(근데 미쳤다고 쓰겠나..)
18. 데이터 멤버들
static 데이터 멤버
는 c에서의 전역변수와 유사하나 특정 클래스에 종속이 된다(싱글턴패턴이 생각ㅇ남.)
클래스 메서드 밖에서 접근하려면 우선 public 형태여야하고 클래스::변수명 으로 사용가능..(cocos의 디바이스 같은..?)
static const 멤버는 객체간에 상숫값을 공유하려고 하는..(말그대로임..)
(심지어 생성된 객체가 하나도 없어도 클래스만 정의되면 객체와 독립적으로 존재함)
참조형 데이터 멤버(포인터 포함)
는 음..포워드 선언의 상황을 생각해보면 됨..두 클래스를 모두 서로가 정의되어 있어서 참조해야하는 상황인 경우..닭이 먼저냐 달걀이 먼저냐의 상황인데..
그러니까 쉽게 설명하면..교차 참조되는 클래스의 헤더파일 중 어느 한쪽(여기선 닭)에 상대편 클래스의 헤더파일(달걀)을 인클루드 하는 대신 포워드 선언을 해두면 컴파일러가 나중에 해당 정의를 찾아다가 타입 매칭...
이때 닭쪽에 참조형 데이터 멤버가 선언됨(포워드 되는 곳에서..)
const 참조형 데이터 멤버
const 메서드만 이용가능함..
19. 메소드 종류들
static 메소드
static 데이터 멤버와 마찬가지로 특정 클래스의 모든 객체에 공ㅈ통적으로 적용되기 위해..(싱글턴패턴..)
: static와 const 를 같이 메소드 선언시 사용할 수 없음! static 메소드명 const // false
static 메소드는 연결된 객체가 없으니까..(특정 클래스에 종속되니..객체가 없음..객체보다 높은..개념?)
this포인터를 이용할수 없음.(심지어 생성된 객체가 하나도 없어도 클래스만 정의되면 객체와 독립적으로 존재함)
const 메소드
반환값 메소드이름 const ; // 말그대로 이 메소드 안에서는 값을 바꾸지 않는 행동만 해야함..
const 객체라 하더라도 소멸은 가능함...
const가 아닌 객체는 const 여부와 관계없이(있던 아니던) 모두 호출이 가능한데..
const 객체는 const메소드만 호출가능함..
20. 디폴트 파라미터는 가장 오른쪽(가장 마지막)에서부터 시작한다..
21. typedef 를 사용하려면 클래스 정의 밖에서 사용해라..
안에서 쓰면 typedef이름조차도 스코프 지정연산자 클래스::를 붙여야함..
22. friend 잠깐 정리..
friend 설정은 객체도 가능하지만 특정메소드도 가능함.. 선언은 권한을 열어줄 클래스에서만 할 수 있음..
다른 클래스에 대해 접근 권한을 요청하는 클래스 또는 메소드 스스로 다른 클래스의friend를 선언할 수는 없음.
그리고 클래스 내에서의 friend 선언은 함수 원형 선언으로 취급되기 때문에 별도로 해당함수의 원형을 선언할 필요가 없음(이건 외부에서 friend로 함수를 설정할때 이야기임!!)
23. is-a 관계는 어떤 클래스를 기반으로 다른 클래스가 존재할 때 적용
상속은 단방향으로 일어난다.
sub 클래스 입장에서는 super클래스와의 관계가 분명하지만 super 클래스 입장에서는 자신을 상속받는 클래스가 무엇인지 전혀 알지 못함.(중요)
즉, super 통해서는 sub의 public 멤버 메서드에 접근 nono(반대는 됨)
포인터 및 참조형으로 객체를 참조할 때는 객체의 클래스는 물론, 부모 클래스 중 어떤 클래스 타입으로도 변수를 선언할 수 있다.
sub의 객체를 부모인 super 타입포인터로 참조 가능.(포인터 참조 둘다)
24. C++11의 상속방지 기능
class 클래스명 final
{}
이러면
class 클래스명 : public 클래스명(위에것) 하면 컴파일 오류 발생!
근데 메소드에 하면? virtual방지 기능!
25. 상속을 받으면서 슈퍼클래스에서 이미 정의된 메서드의 행동을 바꾸는 것은 오버라이딩
메서드 오버라이딩을 제어하는 것이 virtual
메서드의 정의가 클래스에 따라 바뀌기 때문에 어떤 클래스의 객체에서 메서드를 호출했냐에 따라 행동이 틀려짐!(오버라이딩 되지 않은 super 메서드라면 sub객체에서 호출하더라도 super구현부에서 정의한 메서드가 동작,virtual 안하면! 이렇다는것)
또한 포인터나 참조가 슈퍼클래스의 타입이면(A* ptr) 실제 가리키는 객체가 서브클래스라(new B();) 하더라도 서브클래스에만 정의된 멤버/메서드에는 접근할 수 없다.
(포인터나 참조로 sub 객체, B객체를 가르키고 있을 때 그 타입이 super 객체, A객라고 하더라고 메모리를 차지하고 있는 크기는 변하지 않는다. 즉, super 타입으로 참조하고 있어도 그 객체인 sub의 특징들은 메모리에 유지?)
또한 타입 캐스팅이나 대입을 통해 객체가 변경되는 경우에는 원본 객체의 정보가 기억되지 않는다.
아래는 예문 소스
26. C++ 생성순서(소멸자는 반대임)
1. 클래스가 부모 클래스 가졌으면 부모클래스의 생성자가 실행
(보통 부모의 디폴트 생성자가 실행되나 디폴트 생성자가 없으면 자식클래스에서 생성자를 명시적으로 지정해야함)
2. static이 아닌 클래스 멤버들이 선언 순서에 맞게 생성(static은 main 이전에 메모리 할당)
3. 클래스 생성자 바디 실행
사용자로부터의 동적인 파라미터 전달이 필요하다면
sub 생성자 파라미터를 super의 생성자에게 넘겨줄 수도 있다. (sub::sub(int i) : super(i) {} )
하지만 서브클래스의 데이터 멤버를 슈퍼클래스의 생성자 파라미터로 넘기는 것은 제대로 동직하지 않는다.
왜냐면 순서가 (위에봐바) 안되잖냐>?
27. 소멸자는(상속에서) 닥치고 virtual 붙여라..delete할때 큰일ㅇ나니까..
28. 부모 클래스에 있는 메서드를 자식에서 오버라이딩된 메서드에서 실행하려면
::스코프를 부모 클래스로 해두고 사용하면되는데..그보다 비주얼 c++에서는 __super 키워드로 해결
return __super::메서드 ;이런식
29. 다운캐스팅시 dynamic_cast
30. 추상클래스가 있는데..추상 클래스 *ptr 선언이 된다 왜냐고?
나중에 서브클래스의 인스턴스화하면서 초기화되기때문에(추상클래스 *ptr=new 서브클래스();)
31. 상속과 간련된 미묘한 문제들
31-1 오버라이딩을 통한 메서드의 특성변경
31-1-1 리턴 타입변경
메서드를 오버라이딩 할때는 원본 메서드의 선언 즉, 메서드 원형과 완벽히 같게 오버라이딩하는것이 기본(매개변수가 틀리다면 그것은 오버로딩)
그.런.데. C++에서는 오버라이딩하는 메서드의 리턴 타입을 바꿀 수 있는 경우도 있다.
원본 메서드의 리턴 타입이 어떤 클래스에 대한 포인터나 참조형이라면 오버라이딩하는 메서드의 리턴 타입을 원본 리턴 타입 클래스의 서브 클래스에 한하여 바꿀 수 있다.
(전혀 관계없는 타입은 안됨 심지어 void*도 안됨!)
(클래스가 병렬 계층을 가지는 객체를 대상으로 작업할 떄 편리하게 이용된다.)
31-1-2 파라미터의 변경
상속관계에서 파라미터가 다르면 새롭게 만들어진 함수에 의해 가려지게 된다.
(서브에서 만들어졌으면 슈퍼클래스의 메서드가 가려진다)
(또 다른 상황으로 슈퍼클래스에서 메서드의 파라미터가 변경되면 졸지에 서브클래스만의 새로운 메서드가 만들어진다)
(이런 경우를 방지하기 위해선...1. 전부 다 바꾼다. 2. C++11의 기능인 override를 붙인다.
override를 붙이면 해당 메서드가 오버라이딩이 아닌 신규 메서드가 될 수 밖에 없다면(위의 상황이 된다면) 컴파일 오류를 뱉음. 끝.
31-2 메서드 오버라딩의 특수한 경우들
31-2-1 c++에서는 static메서드를 오버라이딩 할 수 없다. 끝.
31-2-2 슈퍼클래스의 메서드가 오버로딩된 메서드인 경우
슈퍼클래스의 메서드가 오버로딩 된 상황
virtual void show()
virtual void show(int i)
이런경우 서브 클래스가 상속받아
virtual void show()를 오버라이딩 한 상황이면
sub 객체를 통해 show(int i)를 호출하려고 하면 슈퍼클래스에 존재함에도(오버로딩으로) 명시적으로 오버라이딩 하지 않았기 때문에 없는것으로 취급 컴파일 에러 발생!
서브객체.show(int i) // false!
그.런.데. 호출할 방법이 있다!?
sub객체를 포인터나 참조형 타입을 통해 super 타입으로 접근하면됨..
super* ptr=⊂
또는 super* ptr=new sub();
ptr->show(int i); 헐..또는 슈퍼클래스로 타입캐스팅해서 쓰면됨..
그냥 버그안만들라면..다 오버라이딩해!!(매개변수가 틀린거 모두다!!)
31-2-3 private 또는 protected로 선언된 슈퍼클래스의 메서드
아래 그냥 예제 참고..이런경우 사용
31-2-4 슈퍼클래스의 메서드가 디폴트 인자 값을 가진 경우
sub 객체를 super 타입 포인터 또는 참조로 접근하여 호출하여 몸체는 sub의 것이 실행되지만 디폴트 인자는 super에서 정의한 것이 적용됨.
(실제 이용된 객체는 보지않고 코드에서 표현된 타입만 보기 때문이다. 디폴트 인자 값을 상속되지 않는다.)
(그래서 항상 같은 값으로 디폴트 인자를 정의해주는것이 바람직하다)
32. 포인터로부터의 참조
void swap(int &a, int &b) 이런 함수가 있는데
(반환값 void 매개변수는 참조형 파라미터 인자다 포인터랑은 헷갈리지말도로ㅗ록)
함수의 인자로 사용할 변수가 포인터라면 포인터를 역참조(*)하여 포인터를 참조형으로 변환하기만하면됨.
원래 포인터를 역참조하면 포인터가 가리키는 값을 얻어오게 되지만 참조형 파라미터의 인자로 쓰일 때는 컴파일러가 파라미터의 초기화에 해당변수를 이용함.
int *a, int *b 라면 swap(*a, *b) 하면되다는것..
33. 함수 / 메서드에서 리턴 타입으로 참조형을 이용할때는 절대로 로컬 변수를 리턴하면 안됨!
(함수 안에서 선언된것 말이야... 함수가 리턴할때 스택과 함께 사라지니까..)
34. 내부링킹 외부링킹
기본적으로 함수나 전역변수는 외부링킹이 적용됨..
내부링킹 : 같은 소스안에서 연결되는 경우
외부링킹 : 외부소스와 연결되는 경우
firstfile.cpp 내부
void f();
int main()
{
f();
}
* f()의 원형은 있지만 정의부가 없음
anotherfile.cpp 내부
#include<iostream>
using namespace std;
void f();
//static void f();
void f()
{
cout << "f" << endl;
}
* f()의 원형과 정의부 모두 존재
위의 각 소스파일은 아무런 에러없이 컴파일 됨 왜냐면 main에서 호출하는 함수 f()는 외부링킹을 통해 다른 파일에서 찾기 떄문이다..
(보통은 이런식으로 함수 원형을 소스파일별로 각각 선언하지 않음..헤더파일에 원형을 선언해서 하는데..그게 편리하기때문임..여기서는 include없음 왜냐면 cpp이니까..h가 아님....)
그런데 static으로 함수를 선언시 위처럼..컴파일 에러뜸..(외부링킹이 안되니..참조를 못혀)
static인 경우 강제로 내부링킹이 됨..똑같은 기능으로 무명 네임스페이스가 있음.
namespace
{
void f();
void f()
{}
}
34. extern 은 static 키워드와 반대로 외부 링킹할 대상을 선언함..
예를들어 const나 typedef는 기본적으로 내부링킹이 적용되나 extern키워드를 만나면 외부링킹이 적용된다.
그런데
extern int x = 3; 이런식으로는 불필요하다..왜냐면 컴파일러가 정의로 취급을 하는데
(물론 정의된 변수를 참조하는 외부파일에서는 유용함.)
전역변수로되어 기본적으로 외부 링킹이 적용되기 때문에..
그래서 이렇게 두번해야함...
extren int x;
int x=3; 이렇게 하면 ...........................
..아 그냥 전역변수 사용하지 말고..static 클래스 멤버로 대체해라..끙..
35. 전역변수의 초기화 순서
모든 전역변수와 static 클래스 멤버변수는 main함수가 실행되기 전에 초기화됨..
class Demo
{
static int x;
}
int Demo::x=3;
int y=4;
여기서 x는 변수 y보다 먼저 초기화됨..
그러나 여러소스 파일에 나뉘어 선언된 전역변수에 대해서는 초기화 순서가 특별히 보증되지 않는다.
보통은 특별히 문제가 안되지만 static 변수나 전역변수가 다른 변수에 의존할 때는 문제가 된다.
(그래서 전역 객체 생성시 그 생성자에서 다른 전역객체 접근하는 행위는 안하는게 좋음..)
36. 함수 포인터의 typedef
c++에서는 virtual있어서 거의 사용안하는데..그래도 알아둬라..필요하니..
int MyFunc(bool b, int n, const char* p); 함수가 있다.
typedef int(*MyFuncProc)(bool b, int b, const char* p);
MyFuncProc Myproc=&MyFunc//이런식으로 활용
36-1. typedef c++ 타입 에일리어스
using MyInt = int;
typedef int MyInt;
와 같다.
그럼 위의 36을 이것으로 하면.. using MyFuncProc = int(*)(bool, int, const char*);
MyFuncProc Myproc=&MyFunc;
37. 캐스트 방법 정리
상황
캐스트 방버
int, double간 변환처럼 허용되는 변환을 명시적
static_cast
const 속성제거
const_cast
커스텀 생성자 등 명시적인 변환
static_cast
전혀 관계없는 두 객체 간의 변화
없음
같은 클래스 계층에 속하는 서로 다른 클래스 객체의 포인터(참조)간 변환
static_cast , dynamic_cast
전혀 관계없는 두 포인터(참조)간의 변환
reinterpret_cast
함수 포인터간의 변환
reinterpret_cast
생략부분
8.6.5 virtual 심화탐구
8.6.6 RTTI typeid?
//런타임에 객체의 타입이 무엇인지 알수 있음?
#include<typeinfo> typeid(객체 매개변수?) //이게 뭔데?
9.1.6 우측값 참조(C++11)
9.2.1.3 constexpr 키워드
'@ 16. 1 ~ 17. 1 > C++' 카테고리의 다른 글
전문가를 위한 C++ 정리(2) (0) 2015.04.13 예외처리 (0) 2015.03.28 멤버 함수가 객체 자신을 리턴하는 경우 (1) 2014.10.27 함수 포인터 배열 (0) 2014.10.27 원형패턴..(개인적 정리 ebook!) (0) 2014.09.02