more effective c++ chapter1 2_dcshin

24
more Effective C++ Chapter 1, 2 ‘기본 개념들’ 그리고 ‘연산자(Operators)’ 131039 신동찬

Upload: dongchan-shin

Post on 08-Jul-2015

937 views

Category:

Engineering


5 download

DESCRIPTION

more effective c++ chapter 1, 2 정리

TRANSCRIPT

Page 1: More effective c++ chapter1 2_dcshin

more Effective C++ Chapter 1, 2‘기본 개념들’ 그리고 ‘연산자(Operators)’

131039 신동찬

Page 2: More effective c++ chapter1 2_dcshin

* -> & .more effective c++ 보실 정도면 이게 단순 기호로 보이지는 않겠죠

포인터와 참조자 그리고 각 연산자 입니다

둘 다주소 값에 대한 정보즉, 다른 객체를 간접적으로 참조하는 것

그러나 다시 생각해보면프로그래밍에 언제 같은 걸 이유 없이 둿던가?

Page 3: More effective c++ chapter1 2_dcshin

주소인 듯 주소 아닌 주소 같은 너

null reference

가 둘을 확실히 구분

참조자(&) 개념에선 null reference가 없다!참조자는 메모리 공간을 차지한 객체를 참조하고 있어야 만 함

반면 포인터(*)는 nullptr로 세팅 가능하기에변수 참조 영역에 객체가 항상 있다는 보장이 없으면 포인터!

Page 4: More effective c++ chapter1 2_dcshin

참조자는 반드시 객체를 참조하고 있어야 함따라서 선언될 때 반드시 초기화 되어야 함

string& rs; //이건 에러

string s(“xyzzy”); //이하 정상string& rs = s;

포인터는 반드시는 아님(그렇다고 초기화는 하는 게 당연한 거!!)

string *ps; //에러는 안난다

string *ps = nullptr; //일반적인 초기화

아니 이거 저거 가리는 것 많은데...그냥 포인터로 통일하면 쉽지 않을까?

쉽다는 것은 뭔가 trade-off 되는 것!성능을 생각한다면 참조자

Page 5: More effective c++ chapter1 2_dcshin

void printDouble(const double& rd){

cout << rd;}

참조자는 null객체를 가질 수 없으니 언제든 통과cout 구문에서 rd는 double을 참조를 보장한다

void printDouble(const double* pd){

if(pd){cout << pd;

}}

포인터는 null 가능성이 존재cout 구문에서 pd가 null이면 유효한 상태가 아님

Page 6: More effective c++ chapter1 2_dcshin

참조자는• 참조 대상 객체를 미리 알고 있을 때• 다른 객체를 바꾸어 참조할 일이 없을 때• 연산자 구현할 때

포인터는 나머지 전체

연산자 함수 구현에서도 반드시 참조자!

vector<int> v(10);

v[5] = 10;

이 구문에서 사용된 operator [] 가 포인터를 반환하면우리가 일반적으로 사용하는 v[5] = 10;을 사용 못함

포인터 반환시 *v[5] = 10;으로 써야 함v가 포인터의 벡터인 것처럼 보이는 혼란 발생

따라서

Page 7: More effective c++ chapter1 2_dcshin

C++의 캐스팅effective c++에 이어 more effective c++에서도 강조C++에게 C++을... C 스타일은 버리자!

• static_cast• const_cast• dynamic_cast• reinterpret_cast

사용 방법

캐스트 형식 <대상 타입> (표현식)

static_cast<double>(firstNumber)

Page 8: More effective c++ chapter1 2_dcshin

static_cast

C 스타일 캐스트와 똑같은 의미와 형변환기본이라 가장 많이 사용될 녀석

더 이상의 설명은...

Page 9: More effective c++ chapter1 2_dcshin

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));

Page 10: More effective c++ chapter1 2_dcshin

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) );

Page 11: More effective c++ chapter1 2_dcshin

reinterpret_cast

함수 포인터 타입을 서로 바꾸는 데 사용

하지만... 강제로 하는 거라사용 하지 말자!

typedef void (*FuncPtr)();FuncPtr funcPtrArray[10];

int doSomething();

funcPtrArray[0] = &doSomething; //에러

funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);

Page 12: More effective c++ chapter1 2_dcshin

상속을 그냥 쓰는 경우도 많지만,우리는 흔히 다형성을 활용해 더 많은 기능을 사용한다

동적으로 파생 클래스 객체를 조작하는 것

일반적으로 사용하지만,파생 클래스 객체에 배열이 있다면 주의가 필요

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);

컴파일에서 에러는 분명히 안 난다!

Page 13: More effective c++ chapter1 2_dcshin

그러나!

잘 동작한다는 보장이 절대 없다!결코 써서는 안 된다

배열 = 포인터 시작 점배열 + i = 메모리 이동

Widget과 SpecialWidget 객체의 크기가 동일한 보장이 있나?즉, 이동하는 범위가 동일하다는 보장이 있나?

일반적으로 파생 클래스가 기본 클래스보다 크기에...정의되지 않은 행동이 나올 수 밖에 없다

생성 / 삭제 모두 문제가 발생한다!

Page 14: More effective c++ chapter1 2_dcshin

생성자도 만들어 사용할 수 있다던데...기본 생성자가 아닌 클래스에서 자주 만나는 문제

class EquipmentPiece{public:

EquipmentPiece(int IDNumber);…};

EquipmentPiece bestPieces[10];

EquipmentPiece* bestPieces = new EquipmentPiece[10];//인자 없이 생성자를 호출해 전부 에러!

해결책

• 배열이 정의된 위치에서 생성자 매개 변수를 직접 삽입• 객체의 배열 대신 포인터의 배열을 사용하고, 해당 포인터에 객체 생성

(인자는 전달하고)• placement new(메모리 지정 new) 사용

Page 15: More effective c++ chapter1 2_dcshin

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);

Page 16: More effective c++ chapter1 2_dcshin

마지막에 왜?

delete[] bestPieces가 아닌가요?new하고 세트로 쓰랬는데...

new[] 연산자 였으면 delete[]와 세트로 사용됨

하지만 replacement new에서 사용하는 new는 연산자가 아님

operator new[] 이므로 operator delete[]와 세트!

Page 17: More effective c++ chapter1 2_dcshin

기본 생성자의 부재는 템플릿 프로그래밍에도 악영향

컨테이너 클래스 객체화 과정에서컴플릿 매개 변수로 들어가는 타입이 기본 생성자가 있어야 함

그래서 기본 생성자를 쓰라는 걸까 말라는 걸까?

기본 생성자가 없는 가상 기본 클래스는 없을 수 없다-> 반드시 써야 한다

기본 생성자로 객체를 초기화하는데 필요한 일을 다 못하는 경우-> 기본 생성자를 변경해 사용

Page 18: More effective c++ chapter1 2_dcshin

operator new[], operator delete[]?

C++연산자는 다음과 같다

다양할 뿐만 아니라, 사용자의 입맛에 맞게

연산자 오버로딩

이 가능하다!

Page 19: More effective c++ chapter1 2_dcshin

물론 다 되는 건 아니고...

여기 12개의 연산자는 오버로딩을 지원하지 않음!

Page 20: More effective c++ chapter1 2_dcshin

연산자 오버로딩을 위해 연산자를 까보면성능 이슈도 발견할 수 있습니다.

여러분은 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;}

바로 전달하는 것 / 선언 과정이 있는 것뭐가 더 좋을지 감이 오시나요?

Page 21: More effective c++ chapter1 2_dcshin

C++의 암시적 타입변환

이것이 가능한 이유는 연산자가 기능을 하기 있기 때문!(단일 일자 생성자/ 암시적 타입변환 연산자)

암시적 타입변환은프로그래머의 의도와 상관 없이 알아서 진행함

프로그래머가 계획치 않은 프로그램을 만들어 냄

따라서 똑같은 일을 하되 다른 이름을 갖는 함수로연산자를 바꿔치기 해야 함

class Rational{public:

Rational(int numerator = 0, int denominator = 1);

operator double() const;};

Rational r(1,2);

cout<<r;

//이 경우 컴파일러는 분수를 출력할 줄 모르기 때문에//알아서 double로 만들어서 0.5로 출력

Page 22: More effective c++ chapter1 2_dcshin

생성자도 연산자이기 때문에...암시적 타입 변환이 충분히 일어날 수 있음

즉 암시적인 변화를 마음껏 날뛰게 둘 수록개발자는 힘들어 질 수 밖에 없음

이런 경우 어떻게 해야될까요?

• 단일 인자 생성자를 선언하지 않음(이건 매번 할 수 없음)

• 컴파일러가 마음대로 호출하지 않도록 함

explicit 키워드를 사용

template<class T>class Array{public:…

explicit Array(int size);…};

Array<int> a(10);

Array<int> b(10);

if(a == b[i])...//에러발생

Page 23: More effective c++ chapter1 2_dcshin

앞에 문제를 해결하니 연산자 오버로딩을 애용합시다?!&&, ||, .에 대해 오버로딩 하면 C++의 기능을 죽이기도 함

단축 평가(short-circuit)처리가 사라짐...

char* p;

if((p != 0) && (strlen(p) > 10))…

코드에서 p가 null이어도 오류가 나지 않는다이미 p!= 0에서 단축 평가가 일어나strlen(p)가 실행되지 않기 때문이다!

오버로딩하면?!함수 호출 구조가 되어 버려 단축 평가는 사라진다.strlen(p)가 실행될 수도 있다 = 프로그램 오류

Page 24: More effective c++ chapter1 2_dcshin

more effective C++ 시작