Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
MEC++ chapter1,2
NHN NEXT 장문익
항목1: pointer와 reference 구분
• reference는 null reference가 없다. 항상 메모리 공간을 차지한
객체를 참조해야 한다.
• 그러므로 어떤 객체를 참조하는 변수를 하나 두고 싶은데, 그 변
수가 참조하는 객체가 없을 수도 있는 경우는 pointer를 사용해
야 한다.
• 반대로 객체를 참조하는 어떤 변수가 가리키는 메모리가 항상
유효한 객체면 reference를 쓴다.
reference
• 반드시 객체를 참조하고 있어야 하기 때문에, 선언될 때 초기화
해야 한다.
• 반면에 pointer 초기화되지 않아도 사용할 수 있다.
reference가 효율적일 수 있다.
다른 객체를 참조할 수 있는가?
참조자를 반드시 써야할 특수한 상황
• operator[] 연산자는 대입 연산자의 좌변으로 쓸 수 있는 값을
반환해주는 것이 보통이다.
• 만약 operator[]가 포인터를 반환하면
• 이 코드는 v가 포인터의 벡터로 보이게 하는 단점이 있다.
항목1 정리
• 참조자는 참조하고자 하는 어떤 객체를 미리 알고 있을때, 다른
객체를 바꾸어 참조할 일이 결코 없을 때, 포인터를 사용하면 문
법상 의미가 어색해지는 연산자를 구현할 때 사용한다.
• 이 세 가지의 경우를 제외하고는 무조건 포인터.
항목2: C++ 스타일의 캐스트
• C 스타일의 캐스트는 어떤 타입을 다른 타입으로 아무 생각없
이 막 바꿔주는 괴물이다.
• C 스타일의 캐스트는 눈으로 찾기 힘들다.
• 그래서 C++ 스타일의 캐스트를 쓰자
static_cast
• C 스타일 캐스트와 같은 의미와 형변환 능력을 지니고 있다.
• const를 떼어내지 못한다.(const_cast로 대신한다.)
• const_cast는 const와 volatile를 제거하는 것 이외의 용도는 없
다.
const_cast
dynamic_cast
• 상속 계층 관계를 가로지르거나 하향시킨 클래스 타입으로 안
전하게 캐스팅할 때 사용한다.
• 기본 클래스의 객체에 대한 포인터나 참조자의 타입을 파생 클
래스 혹은 형제 클래스의 타입으로 변환해준다.
• 캐스팅 실패는 널 포인터(포인터를 캐스팅할 때)나 예외(참조자
를 캐스팅할 때)를 보고 판변할 수 있다.
dynamic_cast와 down casting
dynamic_cast의 제약사항
• 상속 계층 구조를 오갈 때에만 사용해야 한다.
• 가상 함수가 없는 타입에는 적용할 수 없다.
• 상수성 제거에도 쓸 수 없다.(const_cast를 사용해야 한다)
reinterpret_cast
• 이 연산자가 적용된 후의 변환 결과는 거의 항상 컴파일러에 따
라 다르게 정의되어 있다.
• 따라서 이 연산자가 쓰인 소스는 직접 이식이 불가능하다.
reinterpret_cast의 제약사항
항목3: 배열과 다형성은 다르다.
• “다형성을 가지고 있다.”: 기본 클래스 객체의 포인터나 참조자
를 통해 파생 클래스 객체를 조작할 수 있다.
배열은 배열의 첫 요소를 가리키는 포인터
• sizeof(BST)와 sizeof(BalancedBST)는 다르다.
• BST를 기준으로 만들어진 포인터값 계산코드가 BalancedBST에
서 제대로 작동할 것이라 확신할 수 없다.
객체 배열이 삭제될 때는 각 객체의 소멸자가 호출
눈에 보이지 않는 포인터값 계산이 배열 삭제에서 이루어 진다.
객체 배열 삭제와 소멸자 호출
항목 4: 쓸데 없는 기본 생성자는 그냥 두지 말자
• 생성자는 객체를 초기화하는 역할을 맡는다.
• 기본 생성자는 객체가 생성되고 있는 위치의 주변 정보를 전혀
갖지 않고도 객체를 초기화한다.
• 외부 정보 없이 객체가 생성될 수 있는 클래스는 기본 생성자를
가져야 할 것이다.
• 개체를 생성되려면 외부 정보를 요구하는 경우의 클래스는 기
본 생성자를 가지면 안 된다.
기본 생성자가 없는 클래스를 사용
기본 생성자가 없는 클래스의 단점1
• 생성자의 매개변수를 직접 배열에 넣어줄 수는 있는데, 번거로
운 일이다.
객체 배열 대신에 포인터 배열로
• 배열 내의 모든 포인터가 가리키는 객체를 삭제해야 한다.
• EquipmentPiece 객체를 담을 메모리 공간 + 포인터를 담을 공
간 때문에 메모리 사용량이 증가
배열에 대해 직접 비가공 메모리를 할당 후 “메모리 지정 new”
“메모리 지정 new”방법의 단점
• 객체를 삭제할 때에 소멸자를 손으로 직접 호출해야 한다.
• operator delete[]를 호출해서 원래의 비가공 메모리를 직접 해
제해야 한다.
기본 생성자가 없는 클래스의 단점2
• 많은 템플릿 기반의 컨테이너 클래스에 먹일(?) 수가 없다.
• 컨테이너 클래스를 인스턴스화하기 위해서는, 템플릿 매개 변수
로 들어가는 타입이 기본 생성자를 가지고 있어야 하기 때문이
다.
• 예는 다음과 같다.
컨테이너 클래스 인스턴스화
template<class T>
class Array {
public:
Array(int size);
...
private:
T *data;
};
template<class T>
Array<T>::Array(int size)
{
data = new T[size]; //
T::T() 호출
...
}
기본 생성자를 제공하느냐 마느냐?
• 기본 생성자가 없는 가상 기본 클래스는 소프트웨어 개발에 쓰
기 힘들다.
• 가상 기본 클래스의 생성자 매개변수를, 생성되는 객체의 파생
클래스 쪽에서 제공해야 하기 때문
• 그러면 모든 클래스에 기본 생성자를 넣어야 하나?
• 기본 생성자로는 객체를 초기화하는데 필요한 정보를 얻기 힘
들어도 기본 생성자를 넣어야 하나?
초기화 정보가 부족한 기본 생성자
class EquipmentPiece {
public:
EquipmentPiece(int IDNumber = UNSPECIFIED);
...
private:
static const int UNSPECIFIED; // ID가 설정되지 않음을 표시하
는 const
};
Equipment e; // OK
위의 코드는 객체의 멤버가 제대로 초기화되었는지 보장할 수 없다.
쓸 데 없는 기본 생성자는 피하자.
• 기본 생성자에게 확실한 초기화를 기대하지 말자.
• 쓸 데 없는 상황에서는 기본 생성자를 피한다.
• 클래스 생성자가 객체의 멤버 데이터를 확실히 초기화한다.
• 이렇게 하면 멤버 함수를 작성할 때 모든 객체 멤버가 제대로
초기화되었다는 확신을 가질 수 있다.
항목 5: 사용자 정의 타입변환 함수를 주
의
• 문제는 C++ 컴파일러가 스스로 알아서 암시적인 타입변환을
하는 데서 시작한다.
컴파일러가 사용하는 타입변환 함수
• single-argument constructor: 인자를 하나만 받아 호출되는 생
성자.
• implicit type conversion operator
single-argument constructor
• 매개변수가 하나를 받도록 선언되어 있든지,
• 매개변수가 여러 개인데 처음 것을 제외한 나머지가 모두 기본
값을 갖도록 되어 있다.
암시적 변환의 예
class Rational { // 유리수를 위한
클래스
public:
Rational(int numerator = 0,
int
denominator = 1);
...
};
class Rational {
public:
...
operator double() const;
}; // double로 암시적으로 바
꾼다.
Rational r(1, 2); // r은 ½
double d = 0.5 * r; // r이 double
로 변환
암시적 변환의 위험성
Rational r(1, 2);
cout << r; // 1/2를 출력해야 한
다.
• operator << 함수를 작성하지
않아도 출력
• 컴파일러는 operator<< 호출 시
점에 이 함수를 못 찾으면, 함수
호출을 성공시키기 위해 암시적
타입변환 함수를 찾는다.
• 결국, Rational::operator double()
를 찾아서 암시적 변환을 수행
• 문제는 r이 유리수가 아니라 부
동 소수점 실수
함수 바꿔치기로 암시적 변환 막기
class Rational {
public:
...
double asDouble() const;
}; // Rational을 double로
바꾼다.
Ration r(1,2);
cout << r;
cout << r.asDouble();
똑같은 일을 하되 다른 이름을
갖는 함수로 연산자를 바꿔치
기 한다.
불편하지만 잘못된 함수 호출
은 방지
암시적 타입변환 연산자
template<class T>
class Array {
public:
Array(int lowBound, int highBound);
Array(int size);
T& operator[](int index);
...
};
bool operator==(const Array<int>& lhs,
const Array<int>& rhs);
Array<int>a(10);
Array<int>b(10);
...
for (int i = 0; i < 10; ++i) {
if (a == b[i]) { // a[i]가 되어야 한다.
...
}
else {
...
}
}
for (int i = 0; i < 10; ++i) {
if (a == b[i]) { // a[i]가
되어야 한다.
...
}
else {
...
}
}
for (int i = 0; i < 10; ++i)
if (a ==
static_case<Array<int> >(b[i])).
..
컴파일러는 좌측의 코드를 이
렇게 바꾼다.
의도하지 않은 변환이다.
암시적 타입변환 연산자 피하기
• 당연한 소리지만 선언하지 않으면 된다.
• 단일 인자 생성자를 안 쓸 수는 없다.
• 그러면 컴파일러가 이런 연산자를 호출하지 않게 하면 될 것 같
다.
explicit
• 암시적 타입변환의 문제를 막기 위해 만들어 졌다.
• 매개변수와 호출이 명확할 때에만 이 생성자를 호출하라는 의
미
• explicit로 선언된 생성자는 암시적 타입변환에 사용되지 않는다.
• “명시적“ 타입변환은 허용
사용자 정의 타입변환 함수는 두 개 이상 쓰이지 않는다.
template<class T>
class Array {
public:
class ArraySize {
public:
ArraySize(int numElements) :
thesize(numElements) {}
int size() const { return thesize; }
private:
int thesize;
};
Array(int lowBound, int highBound);
Array(ArraySize size); // int -> ArraySize
...
};
for (int i = 0; i < 10; ++i) {
if (a == b[i]) … // error
int – ArraySize, ArraySize – Array<int> 두 번의 변환을 거쳐야 하기
때문에 error가 발생한다.
ArraySize처럼 쓰이는 클래스를 proxy class라 한다.
proxy class는 다른 방법으로는 어떻게 할 수 없는 소프트웨어의 동
작방식을 조정하는데 쓰인다.
항목6: prefix / postfix 형태 구분
class UPInt {
public:
UPInt& operator++(); // prefix
const UPInt operator++(int); //
postfix
UPInt& operator--(); //
prefix
const UPInt operator--(int);
// postfix
UPInt& operator += (int);
...
};
UPInt i;
++i; // i.operator++()
i++; // i.operator++(0)
--i; // i.operator--()
i--; // i.operator--(0)
prefix /
• prefix 증가 연산자: 증가시키고 값을 사용하는 연산자
// prefix
UPInt& UPInt::operator++()
{
*this += 1; // 증가
return *this; // 값을 가져온다.
}
postfix
• postfix 증가 연산자: 값을 사용하고 증가시키는 연산자
// postfix
const UPInt UPInt::operator++(int)
{
const UPInt oldValue = *this; // 값을 가져온다.
++(*this); // 증가
return oldValue; // 가져온 값을 반환
}
사용자 정의 타입은 되도록 prefix 증가
연산자
• postfix 증가 연산자는 반환값으로 쓰기 위한 임시 객체를 만들
어야 하고, 지역 변수로서 생겼다가 없어지는 임시객체
(oldValue)를 대놓고 만든다.
• prefix 증가 연산자는 이런 임시 객체를 사용하지 않는다.
• postfix 증감 연산자는 반드시 prefix 증감 연산자를 사용해서 구
현하라.
항목7: &&, ||, . 연산자는 오버로딩 금지
• short-circuit: 복합적인 Boolean 표현식을 평가할 때, 표현식의
일부가 참 혹은 거짓이란 것이 판명되면, 그 이후의 표현식은
pass
short-circuit을 function call로 대체
// operator&& 오버로딩
if (expression1 && expression2) ...
if (expression1.operator&&(expression2))... // operator&&가 멤버함
수
if (opearator&&(expression1, expression2))... // operator&&가
전역함수
function call은 모든 매개변수를 평가한다.(short-circuit을 하지 않는
다.)
매개변수 평가 순서가 정해져 있지 않다.(short-circuit은 좌에서 우로)
쉼표(,) 연산자
void reverse(char s[])
{
for (int i = 0, j = strlen(s) - 1; i < j; ++i, --j) { // 쉼표 연산자
사용
int c = s[i];
s[i] = s[j];
s[j] = c;
}
}
쉼표 연산자는 왼쪽에 있는 표현식을 먼저 평가하고 오른쪽 평가
쉼표 연산자의 결과값은 결국 오른쪽 –j 값이다.
오버로딩할 수 없는 연산자
오버로딩할 수 있는 연산자
항목8: new와 delete의 의미
string *ps = new string(“Memory Management”);
• 여기서 사용된 new는 new 연산자
• new 연산자는 요청한 타입의 객체를 담을 수 있는 크기의 메모
리를 할당하고,
• 그 객체의 생성자를 호출하여 할당된 메모리에 객체 초기화를
수행한다.
operator new
• new 연산자는 필수적인 메모리 할당을 위해 어떤 함수를 호출하는
데, 이 함수가 operator new이다.
• 이 함수는 다시 작성하든지 오버로딩할 수 있다.
void * operator new(size_t size);
이 함수는 초기화되지 않은 원시 메모리의 포인터를 반환하기 때문에
void*
size_t 매개변수는 할당할 메모리의 크기를 지정
operator new를 오버로딩할 때에는 size_t 이외의 변수를 추가할 수
있는데, 첫째 매개변수는 항상 size_t여야 한다.
new 연산자 작동 방식
• string *ps = new string("Memory Management");
void *memory = operator new(sizeof(string));
*memory에 대해 string::string("Memory Management")를 호출
한다.(컴파일러 담당)
string *ps = static_cast<string*>(memory);
메모리 지정 new(Placement new)
• 할당받은 미초기화 메모리가 있는 경우, 이를 객체처럼 사용하
고자 할 때, operator new의 특별판인 메모리 지정
new(placement new)를 쓴다.
• #include <new> 혹은 #include <new.h>가 필요하다.
메모리 지정 new(Placement new)
class Widget {
public:
Widget(int widgetSize);
...
};
Widget * constuctWidgetInBuffer(void *buffer, int widgetSize)
{
return new (buffer)Widget(widgetSize); // new 연산자가 operator new를
호출하면서
// 매개변
수 buffer를 더 추가
}
메모리 지정 new(Placement new)
void * operator new(size_t, void *location)
{
return location;
}
앞 페이지의 코드는 위와 같이 operator new를 오버로딩한 것이
다.
어쨌든 operator new의 역할은 객체를 만들 메모리를 찾아 그 메
모리의 포인터를 반환하는 것이다.
deletion & deallocation
• 리소스 누수를 막기 위해서는, 할당한 메모리는 반드시 그에 대
응되는 메모리 해제를 통해 운영체제에게 돌려주어야 한다.
• C++에서는 delete 연산자를 사용
• new 연산자와 마찬가지로 내부적인 함수를 사용하는데, 이 함
수가 operator delete
delete 연산자 작동 방식
string *ps;
…
delete ps; // delete 연산자
컴파일러는 ps가 가리키는 객체에 대한 소멸자를 호출하고 그 객
체가 자리잡은 메모리를 해제하는 코드를 만들어 낸다.
operator delete
void operator delete(void *memoryToBeDeallocation);
delete ps;
컴파일러는 이 문장을 보고 아래와 같은 코드를 만든다.
ps->~string(); // 객체 소멸자 호출
operator delete(ps); // 객체가 자리잡은 메모리를 해제
한다.
미초기화 메모리 사용
• 미초기화 메모리만 가지고 일을 할 때에는 new와 delete 연산자가
아닌 operator new와 operator delete를 호출해야 한다.
// 50개 문자를 담을 수 있는 메모리할당. 생성자 호출하지 않음
void *buffer = operator new(50 * sizeof(char));
// 메모리 해제. 소멸자 호출하지 않음
operator delete(buffer);
C++ 버전의 malloc과 free이다.
메모리 지정 new는 소멸자를 직접 호출
하라
void *mallocShared(size_t size);
void freeShared(void *memory);
void *sharedMemory = mallocShared(sizeof(Widget));
Widget *pw = constuctWidgetInBuffer(sharedMemory, 10); // 메모리 지정 new
delete pw; // 결과 예측 불능. sharedMemory는 mallocShared이다.
pw->~Widget(); // 문제 없음. pw가 가리킨 Widget 객체의 소멸자 호출. 메모
리 해제는 않함
freeShared(pw); // 문제 없음. pw가 가리킨 메모리를 해제하지만, 소멸자 호
출 않함.
배열(Arrays)
string *ps = new string[10] // 객체의 배열을 할당
여기에 사용된 new는 new 연산자이다.
하지만 배열 생성에 사용되는 new 연산자는 단일객체 생성에 사
용되는 그것과는 다르다.
배열생성 new 연산자
• 메모리를 할당할 때 operator new 함수를 호출하지 않는다.
• operator new의 배열 할당용 버전인 operator new[]를 호출한
다.(array new라고들 부른다)
• 호출하는 생성자의 개수도 단일 객체 생성용 new와 다르다.
• operator new[]는 배열 요소에 대해 일일이 생성자를 호출해야
한다.
• operator new도 오버로딩이 가능하다.
operator delete []
strind *ps = new string[10]; // operator new[] 호출하여 10개의
string 객체에 대한 메모리 할당
// 배열의 각 요소에 대
해 기본 생성자 호출
delete [] ps; // 배열의 각 요소에 대
해 소멸자 호출
// operator delete[]를
써서 배열 전체 메모리를 해제

More Related Content

More effective c++ chapter1,2

  • 2. 항목1: pointer와 reference 구분 • reference는 null reference가 없다. 항상 메모리 공간을 차지한 객체를 참조해야 한다. • 그러므로 어떤 객체를 참조하는 변수를 하나 두고 싶은데, 그 변 수가 참조하는 객체가 없을 수도 있는 경우는 pointer를 사용해 야 한다. • 반대로 객체를 참조하는 어떤 변수가 가리키는 메모리가 항상 유효한 객체면 reference를 쓴다.
  • 3. reference • 반드시 객체를 참조하고 있어야 하기 때문에, 선언될 때 초기화 해야 한다. • 반면에 pointer 초기화되지 않아도 사용할 수 있다.
  • 5. 다른 객체를 참조할 수 있는가?
  • 6. 참조자를 반드시 써야할 특수한 상황 • operator[] 연산자는 대입 연산자의 좌변으로 쓸 수 있는 값을 반환해주는 것이 보통이다. • 만약 operator[]가 포인터를 반환하면 • 이 코드는 v가 포인터의 벡터로 보이게 하는 단점이 있다.
  • 7. 항목1 정리 • 참조자는 참조하고자 하는 어떤 객체를 미리 알고 있을때, 다른 객체를 바꾸어 참조할 일이 결코 없을 때, 포인터를 사용하면 문 법상 의미가 어색해지는 연산자를 구현할 때 사용한다. • 이 세 가지의 경우를 제외하고는 무조건 포인터.
  • 8. 항목2: C++ 스타일의 캐스트 • C 스타일의 캐스트는 어떤 타입을 다른 타입으로 아무 생각없 이 막 바꿔주는 괴물이다. • C 스타일의 캐스트는 눈으로 찾기 힘들다. • 그래서 C++ 스타일의 캐스트를 쓰자
  • 9. static_cast • C 스타일 캐스트와 같은 의미와 형변환 능력을 지니고 있다. • const를 떼어내지 못한다.(const_cast로 대신한다.) • const_cast는 const와 volatile를 제거하는 것 이외의 용도는 없 다.
  • 11. dynamic_cast • 상속 계층 관계를 가로지르거나 하향시킨 클래스 타입으로 안 전하게 캐스팅할 때 사용한다. • 기본 클래스의 객체에 대한 포인터나 참조자의 타입을 파생 클 래스 혹은 형제 클래스의 타입으로 변환해준다. • 캐스팅 실패는 널 포인터(포인터를 캐스팅할 때)나 예외(참조자 를 캐스팅할 때)를 보고 판변할 수 있다.
  • 13. dynamic_cast의 제약사항 • 상속 계층 구조를 오갈 때에만 사용해야 한다. • 가상 함수가 없는 타입에는 적용할 수 없다. • 상수성 제거에도 쓸 수 없다.(const_cast를 사용해야 한다)
  • 14. reinterpret_cast • 이 연산자가 적용된 후의 변환 결과는 거의 항상 컴파일러에 따 라 다르게 정의되어 있다. • 따라서 이 연산자가 쓰인 소스는 직접 이식이 불가능하다.
  • 16. 항목3: 배열과 다형성은 다르다. • “다형성을 가지고 있다.”: 기본 클래스 객체의 포인터나 참조자 를 통해 파생 클래스 객체를 조작할 수 있다.
  • 17. 배열은 배열의 첫 요소를 가리키는 포인터 • sizeof(BST)와 sizeof(BalancedBST)는 다르다. • BST를 기준으로 만들어진 포인터값 계산코드가 BalancedBST에 서 제대로 작동할 것이라 확신할 수 없다.
  • 18. 객체 배열이 삭제될 때는 각 객체의 소멸자가 호출 눈에 보이지 않는 포인터값 계산이 배열 삭제에서 이루어 진다.
  • 19. 객체 배열 삭제와 소멸자 호출
  • 20. 항목 4: 쓸데 없는 기본 생성자는 그냥 두지 말자 • 생성자는 객체를 초기화하는 역할을 맡는다. • 기본 생성자는 객체가 생성되고 있는 위치의 주변 정보를 전혀 갖지 않고도 객체를 초기화한다. • 외부 정보 없이 객체가 생성될 수 있는 클래스는 기본 생성자를 가져야 할 것이다. • 개체를 생성되려면 외부 정보를 요구하는 경우의 클래스는 기 본 생성자를 가지면 안 된다.
  • 21. 기본 생성자가 없는 클래스를 사용
  • 22. 기본 생성자가 없는 클래스의 단점1 • 생성자의 매개변수를 직접 배열에 넣어줄 수는 있는데, 번거로 운 일이다.
  • 23. 객체 배열 대신에 포인터 배열로 • 배열 내의 모든 포인터가 가리키는 객체를 삭제해야 한다. • EquipmentPiece 객체를 담을 메모리 공간 + 포인터를 담을 공 간 때문에 메모리 사용량이 증가
  • 24. 배열에 대해 직접 비가공 메모리를 할당 후 “메모리 지정 new”
  • 25. “메모리 지정 new”방법의 단점 • 객체를 삭제할 때에 소멸자를 손으로 직접 호출해야 한다. • operator delete[]를 호출해서 원래의 비가공 메모리를 직접 해 제해야 한다.
  • 26. 기본 생성자가 없는 클래스의 단점2 • 많은 템플릿 기반의 컨테이너 클래스에 먹일(?) 수가 없다. • 컨테이너 클래스를 인스턴스화하기 위해서는, 템플릿 매개 변수 로 들어가는 타입이 기본 생성자를 가지고 있어야 하기 때문이 다. • 예는 다음과 같다.
  • 27. 컨테이너 클래스 인스턴스화 template<class T> class Array { public: Array(int size); ... private: T *data; }; template<class T> Array<T>::Array(int size) { data = new T[size]; // T::T() 호출 ... }
  • 28. 기본 생성자를 제공하느냐 마느냐? • 기본 생성자가 없는 가상 기본 클래스는 소프트웨어 개발에 쓰 기 힘들다. • 가상 기본 클래스의 생성자 매개변수를, 생성되는 객체의 파생 클래스 쪽에서 제공해야 하기 때문 • 그러면 모든 클래스에 기본 생성자를 넣어야 하나? • 기본 생성자로는 객체를 초기화하는데 필요한 정보를 얻기 힘 들어도 기본 생성자를 넣어야 하나?
  • 29. 초기화 정보가 부족한 기본 생성자 class EquipmentPiece { public: EquipmentPiece(int IDNumber = UNSPECIFIED); ... private: static const int UNSPECIFIED; // ID가 설정되지 않음을 표시하 는 const }; Equipment e; // OK 위의 코드는 객체의 멤버가 제대로 초기화되었는지 보장할 수 없다.
  • 30. 쓸 데 없는 기본 생성자는 피하자. • 기본 생성자에게 확실한 초기화를 기대하지 말자. • 쓸 데 없는 상황에서는 기본 생성자를 피한다. • 클래스 생성자가 객체의 멤버 데이터를 확실히 초기화한다. • 이렇게 하면 멤버 함수를 작성할 때 모든 객체 멤버가 제대로 초기화되었다는 확신을 가질 수 있다.
  • 31. 항목 5: 사용자 정의 타입변환 함수를 주 의 • 문제는 C++ 컴파일러가 스스로 알아서 암시적인 타입변환을 하는 데서 시작한다.
  • 32. 컴파일러가 사용하는 타입변환 함수 • single-argument constructor: 인자를 하나만 받아 호출되는 생 성자. • implicit type conversion operator
  • 33. single-argument constructor • 매개변수가 하나를 받도록 선언되어 있든지, • 매개변수가 여러 개인데 처음 것을 제외한 나머지가 모두 기본 값을 갖도록 되어 있다.
  • 34. 암시적 변환의 예 class Rational { // 유리수를 위한 클래스 public: Rational(int numerator = 0, int denominator = 1); ... }; class Rational { public: ... operator double() const; }; // double로 암시적으로 바 꾼다. Rational r(1, 2); // r은 ½ double d = 0.5 * r; // r이 double 로 변환
  • 35. 암시적 변환의 위험성 Rational r(1, 2); cout << r; // 1/2를 출력해야 한 다. • operator << 함수를 작성하지 않아도 출력 • 컴파일러는 operator<< 호출 시 점에 이 함수를 못 찾으면, 함수 호출을 성공시키기 위해 암시적 타입변환 함수를 찾는다. • 결국, Rational::operator double() 를 찾아서 암시적 변환을 수행 • 문제는 r이 유리수가 아니라 부 동 소수점 실수
  • 36. 함수 바꿔치기로 암시적 변환 막기 class Rational { public: ... double asDouble() const; }; // Rational을 double로 바꾼다. Ration r(1,2); cout << r; cout << r.asDouble(); 똑같은 일을 하되 다른 이름을 갖는 함수로 연산자를 바꿔치 기 한다. 불편하지만 잘못된 함수 호출 은 방지
  • 37. 암시적 타입변환 연산자 template<class T> class Array { public: Array(int lowBound, int highBound); Array(int size); T& operator[](int index); ... }; bool operator==(const Array<int>& lhs, const Array<int>& rhs); Array<int>a(10); Array<int>b(10); ... for (int i = 0; i < 10; ++i) { if (a == b[i]) { // a[i]가 되어야 한다. ... } else { ... } }
  • 38. for (int i = 0; i < 10; ++i) { if (a == b[i]) { // a[i]가 되어야 한다. ... } else { ... } } for (int i = 0; i < 10; ++i) if (a == static_case<Array<int> >(b[i])). .. 컴파일러는 좌측의 코드를 이 렇게 바꾼다. 의도하지 않은 변환이다.
  • 39. 암시적 타입변환 연산자 피하기 • 당연한 소리지만 선언하지 않으면 된다. • 단일 인자 생성자를 안 쓸 수는 없다. • 그러면 컴파일러가 이런 연산자를 호출하지 않게 하면 될 것 같 다.
  • 40. explicit • 암시적 타입변환의 문제를 막기 위해 만들어 졌다. • 매개변수와 호출이 명확할 때에만 이 생성자를 호출하라는 의 미 • explicit로 선언된 생성자는 암시적 타입변환에 사용되지 않는다. • “명시적“ 타입변환은 허용
  • 41. 사용자 정의 타입변환 함수는 두 개 이상 쓰이지 않는다. template<class T> class Array { public: class ArraySize { public: ArraySize(int numElements) : thesize(numElements) {} int size() const { return thesize; } private: int thesize; }; Array(int lowBound, int highBound); Array(ArraySize size); // int -> ArraySize ... }; for (int i = 0; i < 10; ++i) { if (a == b[i]) … // error int – ArraySize, ArraySize – Array<int> 두 번의 변환을 거쳐야 하기 때문에 error가 발생한다. ArraySize처럼 쓰이는 클래스를 proxy class라 한다. proxy class는 다른 방법으로는 어떻게 할 수 없는 소프트웨어의 동 작방식을 조정하는데 쓰인다.
  • 42. 항목6: prefix / postfix 형태 구분 class UPInt { public: UPInt& operator++(); // prefix const UPInt operator++(int); // postfix UPInt& operator--(); // prefix const UPInt operator--(int); // postfix UPInt& operator += (int); ... }; UPInt i; ++i; // i.operator++() i++; // i.operator++(0) --i; // i.operator--() i--; // i.operator--(0)
  • 43. prefix / • prefix 증가 연산자: 증가시키고 값을 사용하는 연산자 // prefix UPInt& UPInt::operator++() { *this += 1; // 증가 return *this; // 값을 가져온다. }
  • 44. postfix • postfix 증가 연산자: 값을 사용하고 증가시키는 연산자 // postfix const UPInt UPInt::operator++(int) { const UPInt oldValue = *this; // 값을 가져온다. ++(*this); // 증가 return oldValue; // 가져온 값을 반환 }
  • 45. 사용자 정의 타입은 되도록 prefix 증가 연산자 • postfix 증가 연산자는 반환값으로 쓰기 위한 임시 객체를 만들 어야 하고, 지역 변수로서 생겼다가 없어지는 임시객체 (oldValue)를 대놓고 만든다. • prefix 증가 연산자는 이런 임시 객체를 사용하지 않는다. • postfix 증감 연산자는 반드시 prefix 증감 연산자를 사용해서 구 현하라.
  • 46. 항목7: &&, ||, . 연산자는 오버로딩 금지 • short-circuit: 복합적인 Boolean 표현식을 평가할 때, 표현식의 일부가 참 혹은 거짓이란 것이 판명되면, 그 이후의 표현식은 pass
  • 47. short-circuit을 function call로 대체 // operator&& 오버로딩 if (expression1 && expression2) ... if (expression1.operator&&(expression2))... // operator&&가 멤버함 수 if (opearator&&(expression1, expression2))... // operator&&가 전역함수 function call은 모든 매개변수를 평가한다.(short-circuit을 하지 않는 다.) 매개변수 평가 순서가 정해져 있지 않다.(short-circuit은 좌에서 우로)
  • 48. 쉼표(,) 연산자 void reverse(char s[]) { for (int i = 0, j = strlen(s) - 1; i < j; ++i, --j) { // 쉼표 연산자 사용 int c = s[i]; s[i] = s[j]; s[j] = c; } } 쉼표 연산자는 왼쪽에 있는 표현식을 먼저 평가하고 오른쪽 평가 쉼표 연산자의 결과값은 결국 오른쪽 –j 값이다.
  • 51. 항목8: new와 delete의 의미 string *ps = new string(“Memory Management”); • 여기서 사용된 new는 new 연산자 • new 연산자는 요청한 타입의 객체를 담을 수 있는 크기의 메모 리를 할당하고, • 그 객체의 생성자를 호출하여 할당된 메모리에 객체 초기화를 수행한다.
  • 52. operator new • new 연산자는 필수적인 메모리 할당을 위해 어떤 함수를 호출하는 데, 이 함수가 operator new이다. • 이 함수는 다시 작성하든지 오버로딩할 수 있다. void * operator new(size_t size); 이 함수는 초기화되지 않은 원시 메모리의 포인터를 반환하기 때문에 void* size_t 매개변수는 할당할 메모리의 크기를 지정 operator new를 오버로딩할 때에는 size_t 이외의 변수를 추가할 수 있는데, 첫째 매개변수는 항상 size_t여야 한다.
  • 53. new 연산자 작동 방식 • string *ps = new string("Memory Management"); void *memory = operator new(sizeof(string)); *memory에 대해 string::string("Memory Management")를 호출 한다.(컴파일러 담당) string *ps = static_cast<string*>(memory);
  • 54. 메모리 지정 new(Placement new) • 할당받은 미초기화 메모리가 있는 경우, 이를 객체처럼 사용하 고자 할 때, operator new의 특별판인 메모리 지정 new(placement new)를 쓴다. • #include <new> 혹은 #include <new.h>가 필요하다.
  • 55. 메모리 지정 new(Placement new) class Widget { public: Widget(int widgetSize); ... }; Widget * constuctWidgetInBuffer(void *buffer, int widgetSize) { return new (buffer)Widget(widgetSize); // new 연산자가 operator new를 호출하면서 // 매개변 수 buffer를 더 추가 }
  • 56. 메모리 지정 new(Placement new) void * operator new(size_t, void *location) { return location; } 앞 페이지의 코드는 위와 같이 operator new를 오버로딩한 것이 다. 어쨌든 operator new의 역할은 객체를 만들 메모리를 찾아 그 메 모리의 포인터를 반환하는 것이다.
  • 57. deletion & deallocation • 리소스 누수를 막기 위해서는, 할당한 메모리는 반드시 그에 대 응되는 메모리 해제를 통해 운영체제에게 돌려주어야 한다. • C++에서는 delete 연산자를 사용 • new 연산자와 마찬가지로 내부적인 함수를 사용하는데, 이 함 수가 operator delete
  • 58. delete 연산자 작동 방식 string *ps; … delete ps; // delete 연산자 컴파일러는 ps가 가리키는 객체에 대한 소멸자를 호출하고 그 객 체가 자리잡은 메모리를 해제하는 코드를 만들어 낸다.
  • 59. operator delete void operator delete(void *memoryToBeDeallocation); delete ps; 컴파일러는 이 문장을 보고 아래와 같은 코드를 만든다. ps->~string(); // 객체 소멸자 호출 operator delete(ps); // 객체가 자리잡은 메모리를 해제 한다.
  • 60. 미초기화 메모리 사용 • 미초기화 메모리만 가지고 일을 할 때에는 new와 delete 연산자가 아닌 operator new와 operator delete를 호출해야 한다. // 50개 문자를 담을 수 있는 메모리할당. 생성자 호출하지 않음 void *buffer = operator new(50 * sizeof(char)); // 메모리 해제. 소멸자 호출하지 않음 operator delete(buffer); C++ 버전의 malloc과 free이다.
  • 61. 메모리 지정 new는 소멸자를 직접 호출 하라 void *mallocShared(size_t size); void freeShared(void *memory); void *sharedMemory = mallocShared(sizeof(Widget)); Widget *pw = constuctWidgetInBuffer(sharedMemory, 10); // 메모리 지정 new delete pw; // 결과 예측 불능. sharedMemory는 mallocShared이다. pw->~Widget(); // 문제 없음. pw가 가리킨 Widget 객체의 소멸자 호출. 메모 리 해제는 않함 freeShared(pw); // 문제 없음. pw가 가리킨 메모리를 해제하지만, 소멸자 호 출 않함.
  • 62. 배열(Arrays) string *ps = new string[10] // 객체의 배열을 할당 여기에 사용된 new는 new 연산자이다. 하지만 배열 생성에 사용되는 new 연산자는 단일객체 생성에 사 용되는 그것과는 다르다.
  • 63. 배열생성 new 연산자 • 메모리를 할당할 때 operator new 함수를 호출하지 않는다. • operator new의 배열 할당용 버전인 operator new[]를 호출한 다.(array new라고들 부른다) • 호출하는 생성자의 개수도 단일 객체 생성용 new와 다르다. • operator new[]는 배열 요소에 대해 일일이 생성자를 호출해야 한다. • operator new도 오버로딩이 가능하다.
  • 64. operator delete [] strind *ps = new string[10]; // operator new[] 호출하여 10개의 string 객체에 대한 메모리 할당 // 배열의 각 요소에 대 해 기본 생성자 호출 delete [] ps; // 배열의 각 요소에 대 해 소멸자 호출 // operator delete[]를 써서 배열 전체 메모리를 해제