This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<iostream> | |
//#include<mutex> | |
// C++ 스킬22: 변수 정의는 늦출 수 있는 데까지 늦추자 | |
// 변수를 정의하는 부분에서 생성자와 소멸자 | |
//std::string encrypted; // 기본 생성자 호출 | |
//encrypted = password; // 복사 대입 연산자 호출 | |
// | |
//// 선언과 정의를 나눠서 하기보다는 한번에 하는 것이 좋다. | |
//std::string encrypted(password); //이런식으로 쓰는 것이 효율적 | |
// C++ 스킬23: C 스타일의 캐스팅보다는 C++ 스타일의 캐스트를 사용하자. | |
// 1. const_cast : 상수성을 없애는 용도 | |
// 2. dynamic_cast : 안전한 다운캐스팅을 할 떄 사용 | |
// 3. reinterpret_cast : 포인터를 int로 바꿀때 사용 | |
// 4. static_cast : 암시적 변환을 강제로 진행할 때 사용 ( double -> int, void* -> int*, 비상수 객체 -> 상수 객체 ) | |
// | |
//class Widget { | |
//public: | |
// explicit Widget(int size); | |
// | |
//}; | |
// | |
//void doSomeWork(const Widget& w); | |
// | |
//doSomeWork(Widget(15)); // 함수 방식 캐스트 | |
//doSomeWork(static_cast<Widget>(15)); // static_cast 같은 결과 | |
class Point { | |
public: | |
Point(int x, int y); | |
void setX(int newVal); | |
void setY(int newVal); | |
}; | |
struct RectData { | |
Point ulhc; | |
Point lrhc; | |
}; | |
class Rectangle { | |
public: | |
Point& upperLeft() const { return pData->ulhc; } | |
Point& lowerRight() const { return pData->lrhc; } | |
private: | |
std::tr1::shared_ptr<RectData> pData; | |
}; | |
// C++ 스킬24: 예외 안정성을 확보하자 | |
// 두가지 규칙 | |
// 1. 자원이 새도록 만들지 않기 | |
// 2. 자료구조가 더럽혀지는 것을 허용하지 않기 | |
class PrettyMenu { | |
public: | |
void changeBackground(std::istream& imgSrc); | |
private: | |
Mutex mutex; | |
Image* bgImage; | |
int imageChanges; | |
}; | |
void PrettyMenu::changeBackground(std::istream& imgSrc) { | |
lock(&mutex); | |
delete bgImage; | |
++imageChanges; | |
bgImage = new Image(imgSrc); // 사이에서 예외가 나오면 mutex가 잡힌 상태로 남아 자원이 샌다. // 자원관리 클래스를 사용 | |
unlock(&mutex); | |
} | |
// copy and swap 방식으로 예외 안정성을 보장하는 법 | |
// 객체를 수정할 때 객체의 사본을 만들어 놓고 사본을 수정하자 | |
// 사본을 수정하는 과정에서 예외가 생겨도 원본 객체는 유지가 되기 때문에 좋다. | |
// 사본의 수정이 완료된 후에 수정된 객체를 원본 객체를 맞바꾸는 과정은 예외를 던지지 않는 연산 내부에서 수행하면 된다. // 강력한 예외 안정성 보장 | |
// 진짜 객체의 모든 데이터를 별도의 구현 객체에 넣어두고 | |
// 진짜 객체의 데이터 멤버로 구현객체를 가리키는 스마트포인터를 하나 만들어주면 좋다 | |
struct PMImpl { | |
std::tr1::shared_ptr<Image> bgImage; | |
int imageChanges; | |
}; | |
class PrettyMenu { | |
private: | |
Mutex mutex; | |
std::tr1::shared_ptr<PMImpl> pImpl; | |
}; | |
void PrettyMenu::changeBackground(std::istream& imgSrc) { | |
using std::swap; | |
Lock m1(&mutex); | |
std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); //객체의 데이터 부분을 복사 | |
pNew->bgImage.reset(new Image(imgSrc)); // 사본을 수정 | |
++pNew->imageChanges; | |
swap(pImpl, pNew); | |
} | |
// C++ 스킬 25: inline 함수를 활용하자 | |
// inline은 컴파일러에 요청을 하는 것이지 명령이 아니기 때문에 안 붙여도 자동으로 되는 경우도 있다. | |
// 암시적인 방법 : 클래스 정의 안에 함수를 바로 정의 | |
// 명시적인 방법 : 함수 정의 앞에 inline 키워드를 붙이는 것 | |
// 인라인 함수와 템플릿은 대개 헤더 파일 안에 정의한다. | |
// C++에서 함수 인라인은 컴파일 타임에 진행된다. | |
// 함수 인라인으 작고, 자주 호출되는 함수에 대해서만 하자 | |
// C++ 스킬 26: 파일 사이의 컴파일 의존성을 최대로 줄이자 | |
// C++ 클래스 정의는 클래스 인터페이스만 지정하는 것이 아니라 구현 세부사항 (데이터 멤버) 도 포함하고 있다. | |
// string Date 같은 타입의 데이터멤버를 포함한다면 이들을 위한 헤더파일도 포함 되어야 하므로 문제가 된다. | |
// 방법 1. 핸들 클래스 사용 | |
// 방법 2. 인터페이스 클래스 사용 | |
#include<string> // 표준 라이브러리 구성요소는 전방 선언을 할 수 없다. using namespace std{ class string }; 이런식으로 쓸 수 없다. | |
#include<memory> | |
class PersonImpl; | |
class Date; // 전방 선언을 통해 정의부에 대한 의존성을 선언부에 대한 의존성으로 바꾸기 #include "date.h"를 안써도 된다. | |
class Address; | |
// Person 클래스를 두개로 쪼개서 하나는 PersonImpl로 | |
// ----------Person.h-------------// | |
class Person { // pimpl 관용구를 사용하는 클래스 (핸들 클래스)라고 부름 | |
public: | |
Person(const std::string& name, const Date& birthday, const Address& addr); | |
std::string name() const; | |
std::string birthDate() const; | |
std::string address() const; | |
private: | |
std::tr1::shared_ptr<PersonImpl> pImpl; | |
}; | |
// ----------PersonImpl.h-------------// | |
#include <string> | |
#include "date.h" | |
#include "address.h" | |
class PersonImpl { // person 구현 클래스 | |
public: | |
PersonImpl(const std::string& name, const Date& birthday, const Address& addr); | |
std::string name() const { return theName; } | |
std::string birthDate() const { return theBirthDate; } | |
std::string address() const { return theAddress; } | |
private: | |
std::string theName; | |
Date theBirthDate; | |
Address theAddress; | |
}; | |
// ------------- (Person 클래스의 구현) 핸들 클래스에 대응되는 구현 클래스(PersonImpl) 쪽으로 함수 호출을 전달하는 예제 -------------- // | |
#include "Person.h" | |
#include "PersonImpl.h" | |
Person::Person(const std::string& name, const Date& birthday, const Address& addr) | |
:pImpl(new PersonImpl(name, birthday, addr)) {} | |
std::string Person::name() const { | |
return pImpl->name(); | |
} | |
// 인터페이스 클래스 | |
class Person { | |
public: | |
virtual ~Person(); | |
virtual std::string name() const = 0; // 순수 가상함수 가 있으면 자체적으로 인스턴스 만들 수 없으니 팩토리 함수가 필요 | |
virtual std::string birthDate() const = 0; | |
virtual std::string address() const = 0; | |
static std::tr1::shared_ptr<Person> create(const std::string& name, const Date& birthday, const Address& addr); // 팩토리 함수 | |
}; | |
class RealPerson : public Person { // 인터페이스 구현 | |
// 가상함수 전부 구현, 팩토리 함수 구현 해줘야 함 | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
}; |
C++ 스킬 22: 변수 정의는 늦출 수 있는 데까지 늦추자
C++ 스킬 23: C 스타일의 캐스팅보다는 C++ 스타일의 캐스트를 사용하자.
캐스팅은 적게 사용할수록 좋다.
특히 dynamic_cast는 비용이 크므로 성능이 민감한 코드에서는 호출을 피하자.
C++ 스킬 24: 예외 안정성을 확보하자
ㄴ 자원 관리 클래스 사용하기 (스마트 포인터)
ㄴ Copy and swap 방식으로 객체를 수정하기 (swap은 예외를 던지지 않는 연산 내부에서 수행)
C++ 스킬 25: inline 함수를 활용하자
ㄴ 인라인 함수는 함수 호출 비용을 면제해준다.
ㄴ 인라인 함수는 대체로 헤더 파일에 들어 있게 만들자. 대부분의 빌드 환경에서 인라인을 컴파일 도중에 수행하기 때문에
ㄴ inline을 붙여도 inline이 될지 안될지는 컴파일러가 결정한다. 반복문이 있거나 재귀함수이거나 가상 함수 등은 인라인화 해주지 않는다.
C++ 스킬 26: 파일 사이의 컴파일 의존성을 최대로 줄이자
ㄴ 정의 대신에 선언에 의존하게 만들자
ㄴ 1. 핸들 클래스 사용하기
ㄴ 2. 인터페이스 클래스 사용하기
'읽은 책 > Effective C++' 카테고리의 다른 글
11. 템플릿 (0) | 2022.04.27 |
---|---|
10. 상속 (0) | 2022.04.27 |
8. 소프트웨어 설계 (0) | 2022.04.27 |
7. 자원관리 클래스와 스마트 포인터 (0) | 2022.04.27 |
6. 대입 연산자 (0) | 2022.04.27 |