Upload
dongchan-shin
View
933
Download
5
Embed Size (px)
DESCRIPTION
more effective c++ chapter 1, 2 정리
Citation preview
more Effective C++ Chapter 1, 2‘기본 개념들’ 그리고 ‘연산자(Operators)’
131039 신동찬
* -> & .more effective c++ 보실 정도면 이게 단순 기호로 보이지는 않겠죠
포인터와 참조자 그리고 각 연산자 입니다
둘 다주소 값에 대한 정보즉, 다른 객체를 간접적으로 참조하는 것
그러나 다시 생각해보면프로그래밍에 언제 같은 걸 이유 없이 둿던가?
주소인 듯 주소 아닌 주소 같은 너
null reference
가 둘을 확실히 구분
참조자(&) 개념에선 null reference가 없다!참조자는 메모리 공간을 차지한 객체를 참조하고 있어야 만 함
반면 포인터(*)는 nullptr로 세팅 가능하기에변수 참조 영역에 객체가 항상 있다는 보장이 없으면 포인터!
참조자는 반드시 객체를 참조하고 있어야 함따라서 선언될 때 반드시 초기화 되어야 함
string& rs; //이건 에러
string s(“xyzzy”); //이하 정상string& rs = s;
포인터는 반드시는 아님(그렇다고 초기화는 하는 게 당연한 거!!)
string *ps; //에러는 안난다
string *ps = nullptr; //일반적인 초기화
아니 이거 저거 가리는 것 많은데...그냥 포인터로 통일하면 쉽지 않을까?
쉽다는 것은 뭔가 trade-off 되는 것!성능을 생각한다면 참조자
void printDouble(const double& rd){
cout << rd;}
참조자는 null객체를 가질 수 없으니 언제든 통과cout 구문에서 rd는 double을 참조를 보장한다
void printDouble(const double* pd){
if(pd){cout << pd;
}}
포인터는 null 가능성이 존재cout 구문에서 pd가 null이면 유효한 상태가 아님
참조자는• 참조 대상 객체를 미리 알고 있을 때• 다른 객체를 바꾸어 참조할 일이 없을 때• 연산자 구현할 때
포인터는 나머지 전체
연산자 함수 구현에서도 반드시 참조자!
vector<int> v(10);
v[5] = 10;
이 구문에서 사용된 operator [] 가 포인터를 반환하면우리가 일반적으로 사용하는 v[5] = 10;을 사용 못함
포인터 반환시 *v[5] = 10;으로 써야 함v가 포인터의 벡터인 것처럼 보이는 혼란 발생
따라서
C++의 캐스팅effective c++에 이어 more effective c++에서도 강조C++에게 C++을... C 스타일은 버리자!
• static_cast• const_cast• dynamic_cast• reinterpret_cast
사용 방법
캐스트 형식 <대상 타입> (표현식)
static_cast<double>(firstNumber)
static_cast
C 스타일 캐스트와 똑같은 의미와 형변환기본이라 가장 많이 사용될 녀석
더 이상의 설명은...
const_cast 부터는 이야기가 달라진다
표현식의 상수성(constness)이나 휘발성(volatileness)부여 및 제거 전용 캐스트
상속 등 이외에서는 기능이 작동하지 않음
class Widget{…};class SpecialWidget: public Widget{…};
void update(SpecialWidget* psw);
SpecialWidget sw;const SpecialWidget& csw = sw;
update(&csw); // 오류
update(const_cast<SpecialWidget*>(&csw));
dynamic_cast
상속 계층 관계를 가로지르거나하향시킨 클래스 타입으로 캐스팅 할 때 사용
클래스 객체에 대한 포인터나 참조자의 타입을파생 클래스 또는 형제 클래스로 변환
class Widget{…};class SpecialWidget: public Widget{…};
void update(SpecialWidget* psw);
update(dynamic_cast< SpecialWidget* >( pw ));
void updateViaRef( SpecialWidget& rsw );
updateViaRef( dynamic_cast< SpecialWidget& > (*pw) );
reinterpret_cast
함수 포인터 타입을 서로 바꾸는 데 사용
하지만... 강제로 하는 거라사용 하지 말자!
typedef void (*FuncPtr)();FuncPtr funcPtrArray[10];
int doSomething();
funcPtrArray[0] = &doSomething; //에러
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);
상속을 그냥 쓰는 경우도 많지만,우리는 흔히 다형성을 활용해 더 많은 기능을 사용한다
동적으로 파생 클래스 객체를 조작하는 것
일반적으로 사용하지만,파생 클래스 객체에 배열이 있다면 주의가 필요
class Widget{…};class SpecialWidget: public Widget{…};
void printWidgetArray(ostream& s, const Widget array[], int numElements){
for(int I = 0 ; i< numElements; ++i){s<< array[i];
}}
Widget WidgetArray[10];printWidgetArray(cout, WidgetArray, 10);
SpecialWidget SWArray[10];printWidgetArray(cout, SWArray, 10);
컴파일에서 에러는 분명히 안 난다!
그러나!
잘 동작한다는 보장이 절대 없다!결코 써서는 안 된다
배열 = 포인터 시작 점배열 + i = 메모리 이동
Widget과 SpecialWidget 객체의 크기가 동일한 보장이 있나?즉, 이동하는 범위가 동일하다는 보장이 있나?
일반적으로 파생 클래스가 기본 클래스보다 크기에...정의되지 않은 행동이 나올 수 밖에 없다
생성 / 삭제 모두 문제가 발생한다!
생성자도 만들어 사용할 수 있다던데...기본 생성자가 아닌 클래스에서 자주 만나는 문제
class EquipmentPiece{public:
EquipmentPiece(int IDNumber);…};
EquipmentPiece bestPieces[10];
EquipmentPiece* bestPieces = new EquipmentPiece[10];//인자 없이 생성자를 호출해 전부 에러!
해결책
• 배열이 정의된 위치에서 생성자 매개 변수를 직접 삽입• 객체의 배열 대신 포인터의 배열을 사용하고, 해당 포인터에 객체 생성
(인자는 전달하고)• placement new(메모리 지정 new) 사용
Placement new
많은 개발자가 친하지 않다+객체 삭제시 소멸자를 직접 호출해야 됨
하지만 비가공 메모리 할당 방법으로 탁월 void* rawMemory = operator new[](10*sizeof(EquipmentPiece));
EquipmentPiece* bestPieces = static_cast<EquipmentPiece*>(rawMemory);
for(int i =0; i<10; ++i){
new(bestPieces+i) EquipmentPiece(ID Number);}
//삭제시for(int i =9 ; I >= 0 ; --i){
bestPieces[i].~EquipmentPiece();}
operator delete[](rawMemory);
마지막에 왜?
delete[] bestPieces가 아닌가요?new하고 세트로 쓰랬는데...
new[] 연산자 였으면 delete[]와 세트로 사용됨
하지만 replacement new에서 사용하는 new는 연산자가 아님
operator new[] 이므로 operator delete[]와 세트!
기본 생성자의 부재는 템플릿 프로그래밍에도 악영향
컨테이너 클래스 객체화 과정에서컴플릿 매개 변수로 들어가는 타입이 기본 생성자가 있어야 함
그래서 기본 생성자를 쓰라는 걸까 말라는 걸까?
기본 생성자가 없는 가상 기본 클래스는 없을 수 없다-> 반드시 써야 한다
기본 생성자로 객체를 초기화하는데 필요한 일을 다 못하는 경우-> 기본 생성자를 변경해 사용
operator new[], operator delete[]?
C++연산자는 다음과 같다
다양할 뿐만 아니라, 사용자의 입맛에 맞게
연산자 오버로딩
이 가능하다!
물론 다 되는 건 아니고...
여기 12개의 연산자는 오버로딩을 지원하지 않음!
연산자 오버로딩을 위해 연산자를 까보면성능 이슈도 발견할 수 있습니다.
여러분은 for문을 어떻게 쓰나요?
1. for(int i=0 ; i <10 ; i++ )2. for(int i=0 ; i <10 ; ++i )
다른 책에서는 다들 1번을 쓰던데...
//전위 연산UPInt& UPInt::operator++(){
*this += 1;
return *this;}
//후위 연산, 전달 인자 int는 둘의 차이를 표시하기 위한 것임const UPInt UPInt::operator++(int){
const UPInt oldValue = *this;++(*this);
return oldValue;}
바로 전달하는 것 / 선언 과정이 있는 것뭐가 더 좋을지 감이 오시나요?
C++의 암시적 타입변환
이것이 가능한 이유는 연산자가 기능을 하기 있기 때문!(단일 일자 생성자/ 암시적 타입변환 연산자)
암시적 타입변환은프로그래머의 의도와 상관 없이 알아서 진행함
프로그래머가 계획치 않은 프로그램을 만들어 냄
따라서 똑같은 일을 하되 다른 이름을 갖는 함수로연산자를 바꿔치기 해야 함
class Rational{public:
Rational(int numerator = 0, int denominator = 1);
operator double() const;};
Rational r(1,2);
cout<<r;
//이 경우 컴파일러는 분수를 출력할 줄 모르기 때문에//알아서 double로 만들어서 0.5로 출력
생성자도 연산자이기 때문에...암시적 타입 변환이 충분히 일어날 수 있음
즉 암시적인 변화를 마음껏 날뛰게 둘 수록개발자는 힘들어 질 수 밖에 없음
이런 경우 어떻게 해야될까요?
• 단일 인자 생성자를 선언하지 않음(이건 매번 할 수 없음)
• 컴파일러가 마음대로 호출하지 않도록 함
explicit 키워드를 사용
template<class T>class Array{public:…
explicit Array(int size);…};
Array<int> a(10);
Array<int> b(10);
if(a == b[i])...//에러발생
앞에 문제를 해결하니 연산자 오버로딩을 애용합시다?!&&, ||, .에 대해 오버로딩 하면 C++의 기능을 죽이기도 함
단축 평가(short-circuit)처리가 사라짐...
char* p;
if((p != 0) && (strlen(p) > 10))…
코드에서 p가 null이어도 오류가 나지 않는다이미 p!= 0에서 단축 평가가 일어나strlen(p)가 실행되지 않기 때문이다!
오버로딩하면?!함수 호출 구조가 되어 버려 단축 평가는 사라진다.strlen(p)가 실행될 수도 있다 = 프로그램 오류
more effective C++ 시작