Books/Effective C++
[Effective C++] 17. new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자
[Effective C++] 17. new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자
2022.05.28우리가 자원관리 객체를 사용하고 있다고 하더라도 잘못 사용하고 있다면 자원이 누수될 가능성이 있다. 어떻게 사용하면 자원을 더욱 안전하게 사용할 수 있는지 알아보자. 자원의 누수가 발생할 수 있는 코드 void processWidget(std::shared_ptr(new Widget), priority()); processWidget 호출 코드를 만들기 전에 컴파일러는 아래 세 가지 연산을 위한 코드를 만들어야 한다. new Widget 표현식을 실행하는 부분 shared_ptr 생성자를 호출하는 부분 priority()를 호출하는 부분 그리고 C++에서 이 연산의 순서는 컴파일러마다 다르다. 물론 new Widget은 shared_prt생성자의 인자로 넘어가기 때문에 shared_ptr보다는 전에 호출..
[Effective C++] 16. new 및 delete를 사용할 때는 형태를 반드시 맞추자
[Effective C++] 16. new 및 delete를 사용할 때는 형태를 반드시 맞추자
2022.05.25std::string* stringArray = new std::string[100]; ... delete stringArray; 위 코드를 보면 stringArray가 가리키는 100개의 string 객체들 중 99개는 정상적인 소멸 과정을 거치지 못할 것이다. new와 delete 연산자의 동작 new 연산자를 사용해 어떤 객체를 동적 할당하게 되면, 두 가지의 내부 동작이 이루어진다. 1. 메모리가 할당된다. (operator new라는 이름의 함수가 쓰인다. 항목 49, 51 참조) 2. 할당된 메모리에 대해 한 개 이상의 생성자가 호출된다. delete 연산자는 다음과 같은 두 가지 내부 동작이 이루어진다. 1. 기존에 할당된 메모리에 대해 한 개 이상의 소멸자가 호출된다. 2. 그 후에 메모리..
[Effective C++] 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자
[Effective C++] 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자
2022.05.22자원 관리 클래스의 자원 접근 아래 createInvestment로 자원을 만든 후, pInv에 넘겨주는 경우를 생각해보자 shared_ptr pInv(createInvestment()); 그리고 Investment 객체를 사용하는 함수가 다음과 같이 정의되어있다고 하자 int daysHeld(const Investment *p); int days = daysHeld(pInv); 그러나 위와 같이 할 경우 컴파일이 되지 않는데, 이유는 Investment* 타입의 포인터를 원하지만 우리가 넘겨주고 있는 타입은 shared_ptr이기 때문이다. RAII 클래스 객체의 자원 변환 위 문제를 해결하기 위해서는 결국 RAII 클래스 객체(shared_ptr)를 실제 자원(Investment*)으로 변환할 방법이..
[Effective C++] 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자
[Effective C++] 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자
2022.05.22항목 13에서 힙 기반 자원을 해제하는 법을 알아봤는데, 힙에 할당되지 않는 자원이라면 우리는 스스로 자원 관리 클래스를 만들어야 한다. 예를 들어 Mutex 타입의 객체를 조작하는 C API를 사용 중이라고 가정해보자. 해당 API에서 제공하는 함수 중엔 lock, unlock함수를 제공한다. void lock(Mutex* pm); // pm이 가리키는 뮤텍스에 잠금을 건다. void unlock(Mutex* pm); // pm이 가리키는 해당 뮤텍스의 잠금을 해제한다. 우리는 배운 것처럼 자원을 획득하고, 소멸 시에 그 자원을 해제해야 하니 이전에 걸어 놓은 뮤텍스 잠금을 잊지 않고 풀어 줄 목적인 뮤텍스 잠금을 관리하는 클래스를 하나 만든다고 생각해보자. class Lock { public: exp..
[Effective C++] 13. 자원 관리에는 객체가 그만!
[Effective C++] 13. 자원 관리에는 객체가 그만!
2022.05.20우리는 동적 할당한 객체를 사용할 일이 더 이상 없을 때 해제해줘야 한다는 사실을 알고 있다. 아래의 예시를 보자. void f() { Investment *pInv = createInvestment(); ... delete pInv; } 이렇게 하면 f를 벗어날 때 Investment객체를 해제하기 때문에 문제가 없어 보이지만, 중간에 예외가 발생하거나 return 되는 경우 등에 대한 고려는 없음을 알 수 있다. 위와 같은 경우를 막기 위한 방법으로는 자원을 객체에 넣고 그 자원 해제를 소멸자가 맡도록 하고, 소멸자는 실행 제어가 f를 떠날 때 호출되도록 만들면 되는데 이러한 내용을 템플릿 클래스로 만든 것이 스마트 포인터이다. 스마트 포인터 포인터처럼 동작하는 클래스 템플릿으로 사용이 끝난 메모리를..
[Effective C++] 12. 객체의 모든 부분을 빠짐없이 복사하자
[Effective C++] 12. 객체의 모든 부분을 빠짐없이 복사하자
2022.05.18객체의 모든 부분을 빠짐없이 복사하자 객체의 안쪽 부분을 캡슐화된 객체 지향 시스템 중 설계가 잘 된 것들을 보면 복사 생성자와 복사 대입 연산자 두 가지가 있고, 이 둘을 통틀어 객체 복사 함수라고 부른다. 이렇게 컴파일러가 생성한 복사 함수는 객체의 모든 부분을 빠짐없이 복사해주지만, 개발자가 직접 복사 함수를 선언한다면 구현한 복사 함수가 틀린 경우에도 알려주지 않는다.(ex. 모든 객체의 복사가 이루어지지 않는 경우..) 예제 void logCall(const std::string& funcName); // 로그 기록을 만든다. class Customer { public: Customer(const Customer& rhs); Customer& operator=(const Customer& rhs..
[Effective C++] 11. operator=에서는 자기 대입에 대한 처리가 빠지지 않도록 하자
[Effective C++] 11. operator=에서는 자기 대입에 대한 처리가 빠지지 않도록 하자
2022.05.18자기 대입(self assignment) 어떤 객체가 자기 자신에 대해 대입 연산자를 적용하는 것을 뜻한다. class Widget {...}; Widget w; w = w; // 자기에 대한 대입 자기 대입이 생기는 이유는 여러 곳에서 하나의 객체를 참조하는 상태(중복 참조)가 일어나기 때문인데, 따라서 같은 클래스의 객체를 다룰 때는 중복 참조를 고려하여 설계해야 한다. 그리고 이때 조심해야 하는 것이 있는데 밑의 코드를 봐보자. class Bitmap {...}; class Widget { private: Bitmap *pb; // 힙에 할당한 객체를 가리키는 포인터 } Widget& Widget::operator=(const Widget& rhs) // 안전하지 않게 구현된 operator= { ..
[Effective C++] 10. 대입 연산자는 *this의 참조자를 반환하게 하자
[Effective C++] 10. 대입 연산자는 *this의 참조자를 반환하게 하자
2022.05.14C++의 대입 연산은 여러 개가 사슬처럼 엮일 수 있는 성질을 가지고 있다. int x, y, z; x = y = z = 10; 대입 연산이 가진 또 하나의 특성은 우측 연관(right-associative) 연산이라는 점이다. 즉, 위의 대입 연산 사슬은 다음과 같이 우측 z부터 순서대로 대입된다. x = (y = (z = 10)); 대입 연산이 사슬처럼 엮이려면 대입 연산자가 좌변 인자에 대한 참조자를 반환하도록 구현되어 있다. 아래의 예시를 보자. class Widget { public: Widget& operator+=(const Widget& rhs) // +=, -=, *= 등에도 동일한 규약이 적용된다. { return *this; // 좌변 객체(의 참조자)를 반환. 반환 타입은 현재 클래..
[Effective C++] 9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자
[Effective C++] 9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자
2022.05.14먼저 이 단원에서 얘기하고 싶은 내용을 얘기하자면 객체 생성 및 소멸 과정에서 가상 함수를 호출하면 "미정의 동작"을 할 수 있기 때문에 호출하면 안 된다는 것이다. 이런 현상이 발생하는 원인에 관해서 알아보자. 다형성과 가상 함수 base클래스에서 가상 함수 function()을 선언하고, derived클래스에서 가상 함수 function을 오버라이딩 해준 구조를 생각해보자. 이제 메인에서 base b = new drived(); 이후 b.function(); 호출 시 derived의 함수가 호출되는데, 이게 바로 C++의 다형성의 특징이다. 근데 base의 생성자에서 가상 함수인 function을 호출하게 되면 어떻게 될까? 일단 예제를 확인해보자. class Transaction { public: ..
[Effective C++] 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자
[Effective C++] 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자
2022.05.14제목의 의미는 소멸자에서 예외가 빠져나오지 않도록 처리하자는 것을 의미한다. 소멸자가 호출되는 경우 정상적으로 객체가 종료되었을 때 예외처리 메커니즘에 의해 객체가 소멸될 때 위에서 예외처리 메커니즘에 의해 객체가 소멸될 때 또 예외가 발생한다면, terminate함수가 호출되어 프로그램이 종료된다. 따라서 try~catch로 예외를 소멸자 내에 묶어 두어야 한다. 아래의 코드를 보면, class DBConnection { public: static DBConnection create(); // DB 생성 void close(); // 연결 닫기 } ---- class DBConn { public: ~DBConn() { db.close(); // DB 연결 닫기 } private: DBConnection..
[Effective C++] 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자
[Effective C++] 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자
2022.05.13C++의 다형성을 통해 우리는 공통 기능을 가진 하나의 클래스를 기본 클래스로 만들고 적절한 용도에 따라 파생시켜 사용할 수 있으므로, 이렇게 파생시킨 객체를 쉽게 사용하기 위해 이 객체에 대한 포인터를 가져오는 용도로 팩토리 함수를 사용하면 좋을 것 같다. 그럼 팩토리 함수부터 알아보자. 팩토리 함수 새로 생성된 파생 클래스 객체에 대한 기본 클래스 포인터를 반환하는 함수로 파생 클래스의 객체를 new로 할당하기 때문에 메모리를 적절히 삭제해야 한다. 하지만 잘 못 사용하면 문제가 될 수 있는데, 아래의 예시를 보고 문제가 되는 부분을 찾아보자. class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); } class AtomicClock: public Time..
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자
2022.05.09복사 생성자와 복사 대입 연산자를 사용하고 싶지 않을 때 항목 5에서 컴파일러가 은근슬쩍 만들어내는 함수들에 대해 알았으니, 이러한 함수들을 사용하고 싶지 않을 때에 대해서 알아보자. 첫 번째 해결의 열쇠는 다음과 같다. 컴파일러가 생성하는 함수들은 모두 public멤버가 되지만, 직접 선언할 때는 public멤버로 선언해야 한다고 요구하는 부분은 없다는 점이다. 즉, 복사 생성자 및 복사 대입 연산자를 private 멤버로 선언하면 클래스 멤버 함수가 명시적으로 선언되기 때문에 컴파일러는 자신의 기본 버전을 만들 수 없음 private의 접근성을 가지므로 외부로부터 호출도 불가능하여 복사를 할 수 없음 두 가지 이유로 인해 외부에서 복사는 불가능하게 할 수 있다. 하지만 private멤버 함수는 그 클..