2019. 7. 21. 19:22ㆍ개발
C++ 기초 이론 ( 복습용 ) - 1
https://skollhati88.tistory.com/4
C++ 기초 이론 ( 복습용 ) - 1
일도 그만두고 시간이 생겨서 묵혀둔 책들을 다시 보기 시작했다. 일단 이번은 한빛출판사 최호성님의 "이것이 C++이다"를 한번 떼고, 나에게 필요한 부분만 다시 정리하였다. C++을 공부하고자 하시는 분은 "이것..
skollhati88.tistory.com
이어서 C++ 기초 이론 정리를 하도록 하겠습니다.
객체의 관계 규정과 설계
상속
class 파생이름 : 접근제어지시자 부모클래스명
- 파생 클래스의 인스턴스가 생성될 때 기본클래스의 생성자도 호출된다.
- 파생클래스는 기본 클래스의 멤버에 접근할 수 있다.
- 단, private 접근 제어 지시자로 선언된 클래스 멤버에는 접근 할 수 없다.
- 사용자 코드에서는 파생 클래스의 인스턴스를 통해 기본 클래스 메서드를 호출할 수 있습니다.
파생 클래스의 생성자는 먼저 호출 되지만, 실행은 나중에 된다.
메서드 재정의(override)
파생 클래스에 메서드를 새로 정의, 기존 것 무시
파생클래스에서 상위 클래스 메서드 호출법 : 상위클래스명::메서드명
소속명 기입으로 명시적으로 호출 할 수 있다.
재정의의 목적은 기존코드를 없애기보다 한데 묶어 사용하기 위함이다.
참조형식과 실형식
CMyDataEx a;
//&rData 참조형식
//a 실형식
CMyData &rData = a;
//*pData 참조형식
CMyData *pData = new CMyDataEx;
//참조형식이 CMyData이기 때문에 실 형식이 CMyDataEx임에도 CMyData::SetData()가 호출 됨
pData->SetData(5);
상속에서의 생성자와 소멸자
- 생성자의 실행순서는 호출자의 정반대
- 소멸자는 호출순서로 실행
- 재귀호출은 아니지만 비슷한 구조
- 파생클래스는 부모 클래스의 멤버변수에 직접 쓰기 연산을 하지 않는 것이 정답
- 파생클래스 생성자에서 부모 클래스 멤버 변수를 초기화하지 않는다.
- 생성자와 소멸자는 객체 자신의 초기화 및 해제만 생각
vs 에서 호출 스택 확인 : ctrl + alt + c
생성자 상속
C++11 표준에서 등장
다중 정의된 상위 클래스의 생성자들을 그대로 가져오는 문법
class CMyDataEx : public CMyData
{
public
using CMyData::CMyData; // 모든 생성자 상속
}
vs 2013은 불가 / gcc4.8.1이상부터 가능
상속 심화
가상함수
virtual 예약어를 사용한 메서드
파생형식에서 메서드를 오버라이딩하면 과거의 정의가 완전 무시
가상함수는 참조형식이건 실형식이건 실형식의 메서드가 호출된다.
"일반 메서드는 참조형식을 따르고 가상함수는 실형식을 따른다"
특정 가상 함수 재정의 방지 : final
virtual void PrintData() final;
소멸자 가상화
-추상자료형
CMyData *pData = new CMyDataEx;
상위클래스로 하위 파생 클래스를 참조하는 것
delete 연산을 실행할 경우 참조형식의 생성자만 호출되고 실형식의 생성자가 호출되지 않는 문제 발생
→ 소멸자를 가상함수로 선언
가상함수 테이블
모든 클래스는 각자 자신만의 고유한 가상함수 테이블을 가진다.
-임의 기준 주소(vs 프로젝트 세팅)
→ 메모리를 시작하는 주소를 임의로 생성
-늦은 바인딩(동적바인딩)
→ 컴파일 이후 메모리 주소가 결정되는 것
vs ctrl + alt + d 디스어셈블리 바인딩 확인
인터페이스
순수가상 클래스
순수 가상 함수를 가진 클래스
-순수 가상 함수
미래에 정의하도록 선언만 해둔 가상함수
virtual int GetData() = 0;
순수 가상 클래스의 파생 클래스는 반드시 기본 클래스의 가상 순수함수를 재정의 해야한다
인터페이스 상속
보편적으로 가장 많이 사용되는 인터페이스를 상속하도록 함
추상 자료형 사용 예
성능 문제로 생각해 보아야 할 것
- 사용자가 입력할 수 있는 모양의 종류는 몇가지인가
- 사용자가 입력한 모양을 화면에 출력할때 어떤 모양인지에 따라 각기 다른 함수가 호출되도록 코드를 작상할 것인가
- 사용자가 입력한 모양이 무엇인지 판단하기 위해 switch-case 문을 사용 할 것인가
빠른 연산을 필요로 할 때, switch,if문은 비효율적(switch-if문은 사용자 입력 시점에 쓰는 것이 좋음)
Lookup 배열이나 추상 자료형을 이용하는 것이 좋음
추상 자료형을 이용하면 경우의 수가 아무리 늘어난다 하더라도 부하가 크게 늘지 않는다.
virtual void CalcFare() = 0;
class CChild : public CPerson
{
virtual void CalCFare()
{
m_nFare = DEFAULT_FARE * 50 / 100;
}
};
class CTeen : public CPerson
{
virtual void CalCFare()
{
m_nFare = DEFAULT_FARE * 75 / 100;
}
};
class CAdult : public CPerson
{
virtual void CalCFare()
{
m_nFare = DEFAULT_FARE;
}
};
int _tmain(int argc,_TCHAR* argv[])
{
for(auto &person : arList)
{
if(nAge < 14)
person = new CChild;
else if(nAge <20)
person = new CTeen;
else
person = new Adult;
//생성한 객체에 맞는 요금이 자동으로 계산
person->CalcFare();
}
}
if문의 나이에 따른 객체 생성은 입력 부라 상대적으로 여유롭다. 계산 부분에서 가상 함수로 선언된 것에 따라 계산함으로 자연스럽게 결과물이 도출된다.
static_cast<>
상속 관계일때, 파생형식을 기본형식(부모클래스)로 포인팅 할 수 있다.(=추상 자료형과 비슷)
//파생 형식의 객체를 기본 형식으로 포인팅
CMyData *pData = new CMyDataEx;
//기본 형식에 대한 포인터이나 가리키는 대상은 파생형식
//이 사실이 명확하므로 파생 형식에 대한 포인터로 형변환 시도
CMyDataEx *pNewData = static_cast<CMyDataEx*>(pData);
CMyData *pData2 = new CMyData;
CMyDataEx *pNewData2 = static_cast<CMyDataEx*>(pData2);
//실행은 되나, CMyDataEx가 멤버 데이터를 갖지도 않았고, 클래스의 멤버 데이터에 접근도 하지 않았기 때문에 가능
dynamic_cast<>
안쓰는 것이 좋음
어떤 객체의 인스턴스 인지 확인할 때 사용
형변환에 실패하면 NULL을 반환
그러므로 NULL인지 조사하는 RTTI(RunTime Type Informatin(or Identification)이 필요
typeid와 함께 성능을 떨어뜨림
reinterpret_cast<>
C의 강제형변환과 동일 그러므로 그냥 강제형변환을 쓰는 것이 편함
const_cast<>
상수형 포인터에서 const 제거
상속과 연산자 다중정의
생성자 다중 정의와 동일
using CMyData::operator+;
using CMyData::operator=;
다중상속
쓰지 않는 것이 좋음(인터페이스 제외)
- 같은 이름의 함수는 명시적 호출 필요
- 파생 클래스 virtual 적용 → 가상 상속 상속이 겹칠 떄, 하나가 무시
인터페이스 다중 상속
다중 상속이 유일하게 좋은 결과로 나타나는 것
ex) CMyDevice 클래스의 인스턴스가 CMyUSB, CMySerial 두 클래스가 가진 인터페이스 모두 제공
수평적 관계와 집합 관계
friend 함수
friend class 클래스명;
friend 함수원형선언; // 전역으로 클래스 외부에 함수 정의
friend 함수를 통하면 private 멤버에 직접 접근이 가능해진다.
friend 클래스
class CNode
{
friend class CMyList;
}
class CMyList
{
CNode *pNode = m_HeadNode.pNext;
}
집합관계
여럿이 모여 하나가 되는 경우
-Composition 구성
-Aggregation 집합체
Composition
클래스 안에서 타 클래스 생성 - 인스턴스 소멸시 내부 클래스 객체들 모두 소멸
Aggregation
클래스에서 참조, 포인터로 멤버변수 인스턴스를 가짐 - 인스턴스 소멸시 참조된 객체들은 유지
템플릿
클래스 템플릿
클래스를 찍어내는 모양자
template<typename T>
class CMyData {
};
CMyData<int> a(5);
템플릿에서 구조적으로 메모리를 자동 동적 할당 및 해제하여, 복사 생성자나 이동 시멘틱을 지원하므로 성능도 좋다.
또한, 개별 요서에 배열연산자로 접근 가능 할 수 있어 배열과 같이 사용 가능
(배열 연산자 재정의 - 첫 할당 보다 큰 인덱스가 입력되면 자동으로 늘릴 수 있다.)
클래스 템플릿 멤버 선언 및 정의
선언 및 정의가 분리 가능하지만 매번 template<typename 형식>을 써줘야함
템플릿 매개변수
template<typename T,int nSize>
template<typename T,typename t2>
템플릿 매개변수는 멤버변수처럼 클래스 내부에서 어디서든 접근 가능
템플릿 특수화
특별한 형식이 있을 경우, 나머지 다른 형식들과 전혀 다른 코드를 적용
함수 템플릿 특수화
//두개의 변수가 모두 char* 형식이면 이 함수로 대체된다.
template<> //특수화 강조를 위해 <>내부 생략
char* Add(char *pszLeft, char *pszRight)
{
}
template<typename T>
T Add(T a,T b)
{
return a+b;
}
묵시적으로 의미가 전달 되려면 두개의 형식이 모두 동일해야 한다.
클래스 템플릿 특수화
함수 템플릿 특수화와 같지만 클래스 선언에서 <>안에 특수화 대상 자료형을 선언하는 것만 다름
template<>
class CMyData<char*>
{
//T로 쓰던 자료형들을 모두 char*로 해줘야 함
}
클래스 템플릿과 상속
template<typename T>
class CMyDataEx : public CMyData<T>
{
};
스마트 포인터
동적 할당한 인스턴스를 자동으로 삭제해주는 포인터
auto_ptr<>
- 가장 오래됨
- 배열을 지원하지 않음
- 얕은 복사 문제 : 단순 대입 연산자 쓸 경우 복사가 아닌 이동이 된다
2,3번의 문제로 사용하지 않는 것이 좋음
shared_ptr<>
포인팅 횟수가 0이 되면 대상을 삭제
shared_ptr<CTest> ptr1(new CTest);
shared_ptr<CTest> ptr2(ptr1);
//ptr2 = ptr1 해도 됨
ptr1.use_count() // 카운팅 된 수 표시
//auto_ptr과 달리 배열 지원
void RemoveTest(CTest *pTest)
{
delete[] pTest;
}
//ptr이 소멸할때 RemoveTest 자동 호출
shared_ptr<CTest> ptr(new CTest[3],RemoveTest);
//reset() 메서드를 통해 즉시 삭제 가능
unique_ptr
shared_ptr과 유사하지만 오로지 한 포인터로 한대상만 가리킨다
weak_ptr
참조만함 포인팅 카운트에 영향을 주지 않고 삭제시 원데이터에 영향도 없음.
shared_ptr을 변경해서 사용해야 함으로 잘안씀
예외처리
기본활용
try{
if(error발생)
throw errcode;
if(error발생)
throw errocde;
}
catch(int nExp){
}
//throw된 errcode가 nExp로 전달
catch 다중화
throw 문으로 예외를 던질때 값의 자료형이 int인 경우와 char인 경우를 각각 다른 catch문으로 만들 수 있다.
try{}
catch(int nExp){}
catch(char ch){}
예외 클래스
사용자 정의 클래스를 이용해서 예외처리가 가능
class CMyException
{
public:
CMyException(int nCode,const char *pszMsg)
{
m_nErrorCode = nCode;
strcpy_s(m_szMsg, sizeof(m_szMsg),pszMsg);
}
int GetErrorCode() const{ return m_nErrorCode; }
const char* GetMessage() const{ return m_szMsg; }
private:
int m_nErrorCode;
char m_szMsg[128];
};
int _tmain(int argc, _TCHAR* argv[])
{
try{}
catch(CMyException &exp)
{
cout<<"ERROR CODE ["<<exp.GetErrorCode()<<"]"<<exp.GetMessage()<<endl;
}
return 0;
}
스택풀기
함수가 함수를 호출하면 매개변수, 자동 변수, 기타 추가 정보가 모두 메모리 스택에 쌓인다.
디버거에서 '호출 스택'은 이 정보를 보여주는 것
스택을 사용하는 변수를 '자동변수'라고 부릅니다.
Test1() → Test2() → Test3()
Test3()에서 throw를 시행하면 스택 전체 해제
메모리 예외처리
너무 큰 메모리 할당시 오류 발생 → 함수나 연산자는 NULL 반환
그래서 보통 반환값이 NULL인지 비교하는 방식으로 메모리 할당 예외처리 시도
try{
char *m_pszData = new char [nSize];
}
catch(bad_alloc &exp)
{
cout<<exp.what()<<endl;
}
ETC
컨테이너
관리의 방법이나 시스템 일부
람다식과 함수 객체
auto func = [](int nParam)->int
{
return nParam;
}
- (매개변수 리스트)->반환형식 {구문;}
- mutable throw (매개변수 리스트)->반환형식{구문;}
람다를 이용한 함수 매개변수
//std::function 템플릿 클래스를 매개변수로
void TestFunc(char* pszParam,std::function<int(char*,int)> param)
{}
int _tmain(int argc, _TCHAR argv[])
{
::TestFunc("TestFunc()",[](char *pszParam,int nParam)->int
{
return 0;
}
};
함수 객체
Functor 함수 호출 연산자를 다중 정의한 클래스
상속도 가능
class Add
{
public:
int operator()(int a, int b){}
double operator()(double a, double b){}
}
Add adder;
adder(3,4);
adder(3.1,4.1);
람다 캡쳐
내부에서 외부에 선언된 변수에 접근하기 위한 선언
복사캡쳐 - 람다 선언 ([]) 내부에 외부에서 사용할 변수 이름을 직접 작성해 캡쳐
ex) [nData](void)->void
참조캡쳐 - 람다 선언([]) 냅에 외부에서 사용할 참조 변수(&)의 이름을 직접 작성해 캡쳐
ex) [&nData](void)->void
디폴트 복사 캡쳐 - 외부에서 사용될 수 있는 모든 변수 복사로 한꺼번에 캡쳐
ex) [=](void)->void
디폴트 참조 캡쳐
ex) [&](void)->void
auto TestFunc = [nData](void)->void{};
//동일한 선언
auto TestFunc=[nData](void){};
auto TestFunc=[nData](){};
auto TestFunc=[nData]{};
//아래의 조합은 불가
[x,x]
[x,&x]
[&x,x]
[=,x]
이로써 C++ 기초에 대한 정리가 마무리 되었습니다!
개인 복습용이지만.. 참고하실 분들에겐 도움이 되셨으면 좋겠습니다. :)
'개발' 카테고리의 다른 글
C++ Boost Log - MultiThread Log (0) | 2019.08.16 |
---|---|
Boost 기반 CustomObjectPool 개발하기 (0) | 2019.08.01 |
저장 프로시저(Stored Procedure) (1) | 2019.07.21 |
AJAX - 폴링 / 롱폴링 (0) | 2019.07.21 |
C++ 기초 이론 ( 복습용 ) - 1 (0) | 2019.07.21 |