Chapter 3 자원관리


* 자원을 획득(동적할당)한 뒤에는 자원관리 객체에게 넘긴다. 그 후 소멸자를 통해 확실히 해제되도록 한다. 스마트 포인터를 사용하면 좋다.


* 자원관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 한다.


* 스마트 포인터를 매개변수로 넘길 경우에는 get함수를 이용하여 명시적으로 변환 후 넘겨줘야 한다.


* RAII (자원획득 즉 초기화) 클래스가 자원 접근 함수를 열어주는것이 캡슐화의 위배는 아니다. 애초에 RAII 클래스는 데이터 은닉이 목적이 아니기 때문이다.


* new-delete를 사용하면 메모리 할당->생성자 호출->소멸자 호출->메모리 해제 순으로 작동된다.


* new에 []를 사용하여 할당했다면 delete에도 반드시 []를 붙여서 깔끔하게 해제해야 한다.


* 배열 타입은 typedef로 만들지 않는것이 좋다. (ex ; typedef int iArray[4];)


* new로 생성한 객체의 포인터를 스마트포인터에 저장하는 코드는 별도의 한 문장으로 만들어준다. 스마트포인터의 생성자는 explicit로 선언되어 있기 때문에 명시적인 변환만 허용된다.

- ex1) processWidget(new Widget, priority()); (암시적인 변환이므로 컴파일 안됨)

- ex2) processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()) (명시적인 변환이므로 컴파일 가능)

ex2) 와 같은 경우에는 메모리 누수에 대한 위험성에 노출되어있기 때문에 독립적인 문장으로 만들어주고 해당 스마트포인터를 인자로 넘겨준다.

Chapter 2 생성자, 소멸자 및 대입 연산자


* 생성자를 정의해주었다면 기본 생성자를 만들어내지는 않음


* 참조자를 데이터 멤버로 갖고있는 클래스에 대입 연산을 지원하려면 직접 복사 대입 연산자를 정의해 주어야 함.


* 복사 대입 연산자를 private으로 선언한 클래스를 상속한 클래스의 경우, 암시적인 복사대입 연산자를 가질 수 없음.


* 위의 특성을 이용하여 복사 생성자와 복사 대입 연산자를 private에 선언 후 정의를 해버리지 않으면 복사 생성자와 복사 대입 연산자의 사용을 막을 수 있음.


* 링크 시점 에러를 컴파일 시점 에러로 옮길 수도 있음.


* 다형성을 가진 기본클래스에서는 소멸자를 반드시 virtual로 선언할 것. 그렇지 않으면 기본객체 포인터로 객체 삭제를 시도했을 시 기본객체의 소멸자만 호출되어서 자식객체는 삭제되지 않고 메모리 누수가 발생된다. (기본클래스에만 써주면 됨)


* virtual 소멸자를 선언하는 것은 해당 클래스에 가상함수가 하나라도 들어 있는 경우로 한정해야 함. 가상함수가 포함되면 객체의 크기가 커진다.


* 가상 소멸자가 없는 클래스는 STL 컨테이너 타입들이다. 그러니 컨테이너 타입들을 상속받아서 사용하지 말자.


* 소멸자에서 절대 예외가 빠져나가지 않도록 try-catch를 이용하자.


* 예외에 대해 사용자가 반응해야 할 필요가 있다면 반드시 보통의 함수에서 처리하는것이 좋다.


* 파생클래스의 기본클래스가 생성되는 도중의 해당 객체 타입은 기본클래스이다.


* 객체 생성 및 소멸과정에서 절대로 가상함수를 호출하지 말자.


* 미초기화된 데이터 멤버는 정의되지 않은 상태에 있다. 이때문에 객체의 생성 및 소멸시에 가상함수가 파생클래스로 내려가지 않는 것이다.


* 대입 연산자는 *this의 참조자를 반환하도록 한다.


* operator= 에서 자기대입에 대한 처리를 반드시 한다.


* 객체의 복사는 모든 부분을 빠짐없이 복사해야한다. 얕은 복사와 깊은 복사에 대한 문제도 고려해야한다.


Chapter 1 C++에 왔으면 C++의 법을 따릅시다


* const, enum, inline으로 #define을 얼마든지 대신할 수 있다. 또한 대신해서 쓰는게 더 안정성이 좋다.


 정적멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 정의가 아닌 선언이다. 해당 멤버의 주소를 구하거나 할 때에는 정의를 따로 해주어야 한다.


* const가 붙는 위치에 따라 의미가 조금 달라진다.

const int *ip : 타입이 상수, 포인터는 비상수이므로 포인터가 가리키는 대상의 값이 상수화된다. 가리키는 대상이 변경되어도 문제 없음.

int const *ip : 타입이 비상수, 포인터는 상수이므로 포인터가 가리키는 대상 자체가 상수화된다. 가리키는 대상이 변경되면 에러가 발생된다. 가리키는 대상의 값을 변경하는것은 문제 없음.


* 멤버함수에 붙는 const의 역할은 해당 멤버함수가 상수객체에 대해 호출될 함수라는 사실을 알려주는 역할이다.

const 키워드가 있고 없고의 차이만 있는 멤버함수들은 오버로딩이 가능하다.


* 멤버함수가 상수함수라는 의미의 개념은 비트수준 상수성(물리적 상수성)과 논리적 상수성이다.

비트수준 상수성 : 멤버함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 상수임을 인정. (정적멤버 제외)

논리적 상수성 : 객체의 일부 몇비트 정도는 바꿀수있되, 사용자측에서 알아채지 못하게만 하면 상수멤버 자격 인정.


 상수/비상수 멤버함수의 코드중복을 피하기 위해 두번의 캐스팅을 사용하여 코드중복을 없앤다. (비상수 멤버함수 안에서 static_cast로 상수성을 부여하여 상수멤버함수의 함수를 실행한 후 const_cast로 상수성을 뗀 값을 반환하면 됨)


 초기화와 대입은 엄연히 다르다. TextBlock(std::string str) : text(str) {} 이런게 초기화임. 이런 방식은 복사 생성자에 의해 초기화 되는것이 원리임. 기본 생성자로 초기화하고 싶으면 생성자 인수로 아무것도 주지 않으면 됨.


 초기화 리스트에 의해 초기화 될 시, 초기화 되는 순서는 선언한 순서대로 초기화된다. 혼동을 방지하기 위해 선언과 초기화리스트 순서를 동일하게 맞춰주는 것이 좋다.


 비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해진다.

정적 객체 : 전역 객체, 네임스페이스 유효범위에서 정의된 객체, 클래스 안에서 static으로 선언된 객체, 함수안에서 static으로 선언된 객체, 파일 유효범위에서 static으로 정의된 객체.

함수안에 있는 정적 객체를 지역 정적 객체라고 한다. 그 외의 모든것은 비지역 정적 객체.


* 비지역 정적 객체를 하나씩 맡는 함수를 만들어서 참조자를 반환하게 만들어서 사용하면 초기화 되는 순서를 프로그래머가 잡아줄 수 있다.

전위형과 후위형을 함수로 구현한다면 이렇게됨

후위형은 먼저 연산을 하고 값이 증가됨

그러니까 임시변수를 선언해서 임시변수에 값을 대입시켜놓고 실질적으로 값은 증가시켜놓으면 된다. 그러고 임시변수를 반환시키면 된다.

전위형은 값이 증가되고 연산을 하므로, 실질적으로 증가를 시키고 그대로 반환을시키면 된다.

둘의 결정적인 차이는 "후위형은 임시변수를 하나 선언한다" 이고 즉, 이말은 전위형은 작업을 한번만하고 후위형은 작업을 두번을한다(메모리 추가소모) 라는소리이다. 그러니까, 단순증감일 경우에는 전위형을 사용하는것이 메모리낭비를 하지않는 방법이다.

#include <stdio.h>


int& fPlus(int& i)

{

i = i+1;


return i;

}

int lPlus(int& i)

{

int temp=i;

i = i+1;


return temp;

}


int main(void)

{

int a=0, i=1;


a = fPlus(i);

printf("a = %d\t i = %d\n",a,i);


i = 0;


a = lPlus(i);

printf("a = %d\t i = %d\n",a,i);


a = 0;

i = 0;


a = lPlus(i);

//a = i++ + i++; 그렇다면 이것과의 차이는????

printf("a = %d\t i = %d\n",a,i);


return 0;

}


하지만 완벽하지는 않은것이, 구현한 함수대로 동작을 하는 원리라면, 주석처리한 부분과 결과가 동일해야하지만 동일하지가 않다. 이부분에 대해서는 아직도 미스터리.

추가로, (i++ + i++) + ++i 와 i++ + i++ + ++i 의 차이도 모르겠다. 

+ Recent posts