포준 라이브러리의 swap를 봐보자 namespace std { template//std::swap의 전형적인 구현 void swap(T& a,T& b)//a의 값과 b의 값을 맞바꾼다. { T temp(a); a = b; b = temp; } } 복사가 3번이나 일어나는 딱히 특별할게 없는 코드이다. 그런데 만약 swap를 하려는 객체가 어마무시하게 크다면 어떻게될까? 컴퓨터는 갑자기 쓸때없이 엄청난 연산을 할것이다. 그러기 때문에 연산을 최대한 줄이기 위해 pImp(pointer to implementation)이란 뜻을 가진 설계를 차용해볼것이다. class WidgetImp1 { private: int a, b, c;//아마도 많은 데이터가 있다 vectorv;//어쩃든 복사 비용이 높다. pub..
항목 24에서 최종적으로 만든 예제 템플릿으로 만들어보자 template class Rational { private: public: Rational(const T& numerator = 0,//매개변수가 참조자로 전달되는 이유는 const T& denominator = 1);//항목 20을 보면 나옵니다. const T numerator() const;//반환 값 전달이 여전히 값에 의한 전달인 const T denominator() const;//이유는 항목 28을 보면 나오고요. }; template const Rational operator*(const Rational& lhs, const Rational& rhs); 이제 혼합형 수치연산을 해보자 Rational oneHalf(1,2);//Rat..
class Rational { private: int a, b; int sum; public: Rational(int numberator = 0, int denominator = 1); int numerator() const; int denominator() const; }; 우리가 유리수를 나타내는 클래스를 만든다고 치자 그렇다면 덧셈이나 곱셈들의 수치연산을 지원하는것이 좋을것이다. 그리하여 멤버함수로 "operator *()"를 구현하려고 한다. class Rational { public: const Rational operator* const Rational& rhs) const; } 이렇게 구현해 놓고 유리수 곱셈연산을 해 보았다. Rational OneEighth(1,8); Rational on..
웹브라우저를 나타내는 클래스가 있다고 가정하자. 그렇다면 그 클래스 안에는 여러기능의 함수들이 있을것이다. 웹페이지를 보여준다거나, 다운로드 기능함수등등말이다. class WebBrowser { public: void clearCache(); void clearHistory(); void removeCookies(); }; 한번에 처리하고싶은 사람이 있을것이다. 그러면 여기서 두가지 선택지가 있다. 1. 멤버함수로 묶어서 한번에 처리한다. class WebBrowser { private: public: void clearEverything();//clearCache, clearHistory, //removeCookies를 호출한다. }; 2. 비멤버 함수로 묶어서 한번에 처리한다. void clearBr..
우리가 20항목에서 참조자를 사용하면 쓸모없는 연산을 하지 않아도 된다는 것을 기억하는가? 하지만 그게 좋다고 아무곳에나 마구 봍여버리면 문제가 될수있다. 다음 코드를 봐보자 const Rational& operator* (const Rational& lhs, const Rational& rhs) { Rational result(lhs.n * rhs.n, lhs.d * rhs.d); return result; } 지역변수로 정의된 result 변수가 해당 함수가 끝나고 참조자가 붙은 상태로 반환되면은 쓰레기값만 가리키고 있는 참조자를 얻게 될 것이다. 이유는 모두들 알고있듯이 해당 블럭에서 선언된 객체는 그 블럭이 끝날 시 할당이 해제되어버린다. 이어서 다음 문제점도 봐보자 const Rational& ..
기본적으로 C++는 함수에 객체를 전달할 때 C와 똑같이 값에 의한 전달을 사용한다. 특별히 따러 지정해주지 않은 한은 "사본"을 쓴다는 것이다. 하지만 여기서 "사본"을 쓸 때 문제가 생긴다. 첫 번째로는 연산을 쓸 때 없이 증가시킬 수도 있다는 것이다. 다음 코드를 봐보자 class Person { private: string name; string address; public: Person() {} virtual ~Person() {} }; class Student : public Person { private: string schoolName; string schoolAddress; public: Student() {} ~Student() {} }; bool validateStudent(Stude..
클래스를 만든다는 것은 새로운 타입을 만든다는 것이나 다름이 없다. 그러기에 '잘' 만들어야 한다. 잘 만들기 위해서 다음과 같은 항목이 제대로 내 코드에 적용이 됐는지 확인해 보자 1. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가? 이 부분이 어떻게 되느냐에 따라 클래스생성자 및 소멸자의 설계가 바뀐다. 그리고 만약 메모리 할당함수를 operator new, operator new[], operator delete, operator delete[])직접 작성한 경우에도 이들 함수 설계에 영향을 끼친다. 2. 객체 초기화는 객체 대입과 어떻게 달라야 하는가? 클래스의 초기화와 대입은 호출되는 함수부터가 다르다. 완전 다른개념이니 잘 구분하자 3. 새로운 타입으로 만든 객체가 값에 의..
C++는 발에 치이고 손에 잡히는것이 인터페이스다. 함수, 클래스, 템플릿 또한 인터페이스 이며 인터페이스는 사용자가 나의 코드와 만리장성을 쌓은 접선수단이다. 그러므로 제목처럼 설계해야한다. 예시를 보자 class Date { public: Date(int month, int day, int year); .....; }; 이렇게 설계를 해 놓으면 month에 넣어햐 할것은 day에 넣거나 year값을 month에 넣거나 실수를 할 수 도있다. 그러므로 다음과 같이 설계를 해 보자 struct Day { int val; Day(int d) : val(d){} }; struct Month { int val; Day(int d) : val(d){} }; struct Year { int val; Year(in..
우리가 비밀번호를 입력받고 입력받은 비밀번호가 너무 짧으면 logic_error예외를 던지도록 하고 그렇지 않으면 비밀번호를 암호화하는함수를 다음과 같이 만들었다고 하자 #include #include using namespace std; enum{MinimumPasswordLength = 8}; string encryptPassword(const string& password) { string encrypted;//B순으로 보겠다. int _tmain() //A방법 { Widget w; for (int i = 0; i < n; i++) w = i; } int _tmain()//B방법 { for(int i=0;i
데이터 멤버는 전부 private로 놔야 한다. 그래야만 문법적 일관성과 캡슐화가 제대로 되기 때문이다. 예를 들어보자 public는 모든 곳에서 접근할 수 있다. 만약 public 멤버 데이터를 누군가가 참조했으면 나중에 고칠 때 그 참조했던 것과 참조한 것을 참조한 것을 모두 고쳐야 한다. 그리고 protected도 마찬가지이다. protected는 상송된 관계 모드가 참조 가능하다 그렇다면 이것도 똑같이 수정 시에 모두 수정하여야 한다. 이제 private에 대해서 말하겠다. 아까 무법의 일관성을 지키려면 private를 사용하라고 한 것 기억하는가? 난 이게 확 와 닿지 않았다. 하지만 정교한 제어가 가능하다고 하니까 그때서야 느낌이 "파밧!" 왔다. 는 구라고 소스코드 보고 아 "그런갑다~" 했..
잠시 포스팅 중단할게 effective c++을 3장까지 읽었는데 거의 대부분의 항목들을 아슬아슬하게 이해하는 수준밖에 안돼서 4장으로 가기 전에 다시 되짚어 보려고 해. 그래서 그동안은 effective c++ 카테고리에선 포스팅 중단할게 그리고 영어 공부도 조금씩 해야할거같아 정말로.. 초등학교 3학년 때부터 영어 놔서 진짜 하나도 모르거든 근데 여기서는 영어가 거의 필수라고 하더라고 어쩔 수 있겠어~ 돈 벌라면 해야지 머리가 좋았으면 좋겠똬!
Widget이라는 클래스가 있고 우선순위를 반환하는 priority() 라는 함수와 shared_ptr객체와 우선순위를 매개변수로 받는 processWidget()함수가 있다고 하자(아래 코드를 보자) class Widget { public: }; int priority(){return 1;}; void processWidget(shared_ptr pw,int priority) { cout
밑의 코드를 보고 잘못된 점을 찾아보자 int _tmain(int argc, char** argv) { string *stringArray = new string[100]; delete stringArray; } 잘못된점이 보이는가? delete를 보면 잘못된 점을 찾을 수 있다. new 로 배열을 하당했는데 delete[]가 아닌 delete이다. 이렇게 되면 문제가 무엇이냐 하면 delete가 할당한 데이터가 배열인지 아닌지 자동으로 확인하지 못한다는 것이다. 100개의 배열을 할당했는데 delete를 사용하면 나머지 99개는 붕~ 뜨는것이다. 그러므로 "나는 배열을 할당해제할 것이다~" 라는 것을 알리기위해 delete 앞에 []를 써주는것이다. 여기서 궁금한점! "delete[]은 배열의 크기를 ..
우리가 shared_ptr로 객체를 만들고 그 객체 안에 있는 포인터를 매개변수로 넣어 주려고 한적 있을 것이다. 근데 그건 안된다. 암시적인 형변환이 안되기 때문에 get() 함수를 써야지 인자를 제대로 넘겨줄 수 있다. class TEST1 { private: int i; public: TEST1():i(7777) {} TEST1(const TEST1& rhs) {} void TestFunc(const TEST1* const rhs) const { cout i TestFunc(t1);//error t1->TestFunc(t1.get());//이건 정상작동 //t1에 들어있는 실제 포인터를TestFunc에 넘김 } 왜냐하면 코드상의 t1은 shared_ptr"클래스"로 만들어진 것이다 그러기 때문에 형..
우릭가 복사 생성자와 복사 대입연산자를 직접 선언하지 않으면 컴파일러가 자동으로 만든다는 것 기억하는가? 컴파일러가 자동으로 만들어주는 복사 생성자와 복사 대입연산자는 객체의 모든 부분을 빠짐없이 복사해준다. 하지만 우리가 자동으로 만들어 주는것이 맘에 들지 않아 직접만들면 우리가 하나하나 다 복ㄱ사를 해 주어야한다. 만약 객체 안에 데이터가 추가된다면 복사 생성자, 복사 대입연산자에서 복사해주는 코드를 추가해줘야한다. 하지만 한가지 문제점이 있다. 만약 우리가 파생 클래스를 복사한다고 했을 때 파생 클래스만 복사학다면 그것은 완전한 복사가 아닌 부분 복사나 마찬가지이다. 하지만 쉽게 기본 클래스까지 복사하는 방법이 있다. 코드를 보자 class TEST1 { private: int nData; publ..
예외 안전성을 확보하는 작업은 매우 힘들다. 다음 예쁜 배경 그림을 깔고 나오는 GUI 메뉴를 구현하기 위해 클래스를 하나 만든다고 가정하겠다(병행성 제어를 위해 Mutex사용) class Image { public: Image(const istream& imgSrc) {} }; class PrettyMenu { private: Mutex mutex;//이 객체 하나를 위한 뮤텍스 Image* bgImage;//현재의 배경 그림 int imageChanges;//배경 그림이 바뀐 횟수 public: ...; void ChangeBackground(const istream& imgSrc);//배경 그림을 //바꾸는 멤버 함수 ...; }; void PrettyMenu::ChangeBackground(con..
세상의 모든 자원은 힙에서만 생기지 않는다. 그리고 힙에서 생기지 않은 자원은 스마트포인터로 처리하지 않는 게 일반적인 견해이다. 항상 그런 것만은 아니지만 우리가 직접 자원관리 클래스를 만들어야 할 때가 있다. 예시를 봐보자 뮤텍스 잠금을 관리하는 클래스를 하나 만들고 싶은데 이전에 걸어놓은 뮤텍스를 잊지 않고 풀어주기 위해서 이 코드를작성했다고치자 class Lock { public: explicit Lock(Mutex* pm) : mutexPtr(pm) { lock(mutexPtr);//자원을 획득한다. } ~Lock() { unlock(mutexPtr); }//자원을 해제한다. void lock(Mutex* pm) {} void unlock(Mutex* pm) {} private: Mutex* m..
자기 대입이란 (self assignment) 어떤 객체가 자기 자신에 대해 대입 연산자를 적용하는 것을 말한다. 아래의 코드처럼말이다. class Widget{...}; Widget w; ... w = w;//자기에 대한 대입 이 때 조심해야 하는것이 있다. 밑의 코드를 봐보자 class Bitmap { ... }; class Widget{ private: Bitmap* pb;//힙에 할당할 객체를 가르키는 포인터 public: ... }; Widget& Widget::operator=(const Widget& rhs) //안전하지 않게 구현된 operator= { delete pb;//현재의 비트맵 사용을 중지한다. pb = new Bitmap(*rhs.pb);//이제 rhs의 비트맵을 //사용하도록..
Widget& operator = (const Widget& rhs) Widget& operator += (const Widget& rhs) Widget& operator-= (const Widget& rhs) Widget& operator*= (const Widget& rhs) Widget& operator/= (const Widget& rhs) Widget7 oeprator=(int rhs) 뭐 이런것들 등등 전부다 반환을 *this로 해주자 관례라고 하니까 따라야지 이것만은 잊지 말자! 1. 대입 연산자는 *this의 참조자를 반환하도록 만들어라
밑의 코드를 봐보자 class Transaction//모든 거래에 대한 {//기본 클래스 public: Transaction(); virtual void logTransaction() const = 0;//타입에 따라 달라지는 //로그 기록을 만든다. ....; }; Transaction::Transaction()//기본 클래스 생성자의 구현 { ...; logTransaction();//마지막 동작으로, 이 거래를 }//로깅을 구현한다. class BuyTransaction : public Transaction//Transaction 파생 클래스 { public: virtual void logTransaction() const;//이 타입에 따른 거래내역 ....;//로깅을 구현한다. }; class ..
아래 코드를 보자 class Widget { public: ...; ~Widget() { ...; }//이 함수로부터 예외가 발생된다고 가정하자 }; void doSomething() { std::vector v; }//v는 여기서 자동으로 소멸한다. 위의 코드에서 Widget형인 vector가 10개가 있다고 치자 그런데 첫 번째 Widget을 제거하려는데 소멸자에서 예외가 터져버렸다. 근데 아직 9개나 남아있으므로 다음 것을 또 제거하려 했는데 또 소멸자에서 예외가 터져버렸다. 어떠한 조건에 의해 예외가 동시에 터져서 C++ 이 감당하기 어려워진다면 프로그램이 꺼진다거나 오작동을 보일 것이다. 다른 예시를 보자 class DBConnection { public: ... static DBConnecti..
항목5 에서 일반 생성자는 막았지만 복사 생성자와 복사 대입 연산자를 막지 못한걸 기억하는가? 우리는 "컴파일러가 생성한 복사 함수는 기본 클래스에 대응한 버전을 만들어낸다" 라는 것을 이용하여 복사 생성자와 복사 대입 연산자를 막아보려고한다. 다음 코드를 봐보자 #include #include using namespace std; class UnCopy { private: UnCopy(const UnCopy&); UnCopy& operator=(const UnCopy&); protected: UnCopy() {} ~UnCopy() {} }; class HomeForSale : private UnCopy { private: public: HomeForSale() {} ~HomeForSale() {}; }..
클래스가 비어있지만 어떤 조건을 충족하면 컴파일러는 클래스를 훓고 지나갈 때 클래스에 무언가들을 만들어 낸다 그 무언가는 생성자, 소멸자,복사 생성자, 복사대입연산자 이다. 이들은 모두 public이면서 inline함수이다. 다음 코드를 보면 컴파일러가 무언가를 만들어 버리는 조건을 볼 수 있다. //자동 생성되는 함수들 class Empty { public: Empty() {};//기본 생성자 Empty(const Empty& rhs) {};//복사 생성자 ~Empty() {};//소멸자: 자동으로 생성된 소멸자는 //가상으로 선언이 안되어있다. Empty& operator=(const Empty& rhs) {};//복사 대입 연산자 }; //조건 Empty e1;//기본 생성자, 그리고 //소멸자 E..
c++의 객체(변수)는 자동으로 초기화가 중구난방적으로 하는 것은 아니다 하지만 초기화가 일어나는 조건을 다 알려면 힘들고 실수도 할 수 있기 때문에 일단은 다 초기화 하자 int x = 0;//int의 직접 초기화 const char* text = "A C-style string";//포인터의 직접 초기화 //(항목 3도 참조) double d;//입력 스트림에서 읽음으로써 std::cin >> d;//"초기화" 수행 그리고 초기화와 대입을 헷갈리지 말자 일단은 대입코드의 예시를 보겠다 class PhoneNumber { .... }; class ABEntry// ABEntry = "Address Book Entry" { private: std::string theName; std::string the..
const는 소스코드 수준에서 의미적인 제약을 주어 나의 실수나 컴파일러가 제약을 위반하는 것을 막는데 한 몫 한다 const는 전역 혹은 네임스페이스 유효범위의 상수 선언(정의), 파일, 함수, static으로 선언한 객체에도 붙일 수 있으며 클래스 내부의 경우에는 정적 멤버, 비정적 멤버 모두 가능하다. 그리고 포인터를 상수로도 바꿀 수 있고 포인터가 가리키는 데이터를 상수로 할 수 있다. char *p = greeting//비 상수 포인터 //비 상수 데이터 const char* p = greeting//상 수 데이터 char* const p = greeting//상수 포인터 const char* const p = sreeting//상수 포인터, 상수 데이터 이번에는 반복자(iterator)를 예시..
우리가 아래의 코드를 썼다고 가정해보자 #define ASPECT_RATIO 1.234 이 상태에서 위의 매크로에 의해 오류가 생기면 기호식 이름인 'ASPECT_RATIO'가 아닌 상수 '1.234'가 보이게 된다. 이유는 소스코드가 컴파일러에 가기 전에 전 처리자가 위의 매크로를 상수로 바꾼 후 넘겨버리기 때문에 컴파일러는 컴파일러가 쓰는 기호 테이블에 위의 'ASPECT_RATIO'를 찾을 수 없기 때문이다. 이 문제의 해결법은 매크로 대신 상수를 쓰는 것이다. const double AspectRatio = 1.234; 이 방법을 쓰면 컴파일러가 쓰는 기호 테이블에서 상수 이름(AspectRatio)을 볼 수 있게 되어 오류를 수정하기 쉽게 된다. 추가적으로 컴파일을 거친 후의 최종 코드가 더 작..