All
[Effective C++] 26. 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자
[Effective C++] 26. 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자
2022.06.18생성자 혹은 소멸자를 끌고 다니는 타입으로 변수를 정의하면 반드시 물게 되는 비용이 두 개 있다. 프로그램 제어 흐름이 변수의 정의에 닿을 때 생성자가 호출되는 비용, 변수가 유효 범위를 벗어날 때 소멸자가 호출되는 비용이다. 이러한 비용은 변수가 정의만 돼도 부과된다. 예제를 보자. std::string encryptPassword(const std::string& password) { using namespace std; string encrypted; if(password.length() < MinimumPasswordLength) { throw logic_error("Password is too short"); } return encrypted; } encrypted를 안 쓴다고 할 순 없지만, ..
[Effective C++] 25. 예외를 던지지 않는 swap에 대한 지원도 생각해 보자
[Effective C++] 25. 예외를 던지지 않는 swap에 대한 지원도 생각해 보자
2022.06.12std의 표준 swap namespace std { template void swap(T& a, T& b) { T temp(a); a = b; b = temp; } } 위에서 보는 것처럼 표준 swap은 복사 생성자와 복사 대입 연산자를 통해 이루어지는데, 복사만 지원하는 타입이라면 어떤 객체든 맞바꾸기 동작을 수행해주며 타입에 상관없이 1번 호출에 3번 복사가 일어난다. (a에서 temp, b에서 a, temp에서 b) 표준 swap의 단점 위에서 말한 것처럼 표준 swap은 1번 호출에 3번 복사가 이루어지기 때문에 다른 타입의 실제 데이터를 가리키는 포인터가 주성분 타입일 경우 복사 시 손해를 보게 된다. 이런 개념을 설계의 미학으로 끌어올려서 사용하는 기법이 pumpl 관용구이다. class Wi..
[Effective C++] 24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자
[Effective C++] 24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자
2022.06.12타입 변환 예시 이전 내용에서 암시적 타입 변환을 지원하는 것은 좋지 않다.라는 내용을 기억할 수 있다. 하지만 가장 흔한 예외 중 하나가 숫자 타입을 만들 때이다. 예를 들어 유리수를 나타내는 클래스를 만들고 있다면, 정수에서 유리수로의 암시적 변환은 허용하자고 판단하더라도 크게 이상하지 않다. 아래의 예시를 봐보면, class Rational { public: Rational(int numerator = 0, int denominator = 1); // 암시적 변환을 위해 explicit를 붙이지 않음. int numerator() const; int denominator() const; private: .... }; 위의 클래스는 int에서 Rational로의 암시적 변환을 허용한다. 이 클래스에서..
[Effective C++] 23. 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자
[Effective C++] 23. 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자
2022.06.12캡슐화의 의미 어떤 것을 캡슐화하면, 우선 외부에서 이것을 볼 수 없게 된다. 그리고 캡슐화한 것들이 늘어나면 그만큼 밖에서 볼 수 있는 것들이 줄어드므로 다른 것들을 바꿀 때 필요한 유연성이 커진다. 즉, 해당 객체의 데이터에 접근할 수 있는 함수가 많을수록 캡슐화 정도는 낮다. 멤버 함수 vs 비멤버, 비프랜드 함수 class WebBrowser { public: void clearCache(); void clearHistory(); void removeCokies(); }; // 멤버 함수 - 한번에 처리 class WebBrowser(){ public: void clearEveryThing(); }; // 비멤버 함수 void clearBrowser(WebBrowser& wb) { wb.clear..
[Effective C++] 22. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자
[Effective C++] 22. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자
2022.06.12데이터 멤버를 private로 선언해야 하는 이유 1. 문법적 일관성 공개 인터페이스가 전부 함수뿐이라면, 클래스의 멤버에 접근할 때 괄호를 쓸지 말지 고민할 필요가 없다. 2. 세밀한 접근 제어 변수가 public이라면 읽기 및 쓰기 모든 것을 할 수 있지만, private로 만들면 읽기 전용, 쓰기 전용, 읽기 및 쓰기 전용 형식으로 구현할 수 있다. 즉, 접근 권한을 세밀하게 컨트롤 가능하다. class AccessLevels { public: int getReadOnly() const { return readOnly; } void setReadWrite(int value) { readWrite = value; } int getReadWrite() const { return readWrite; } ..
[Effective C++] 21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자
[Effective C++] 21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자
2022.06.03함수 수준에서 새로운 객체를 만드는 방법은 스택에 만들거나(지역변수), 힙에 만드는 것(동적 할당) 두 가지뿐이다. 함수에서 참조자를 반환할 때의 문제점 1. 스택 const Rational& operator*(const Rational& lhs, const Rational& rhs) { Rational result(lhs.n * rhs.n, lhs.d * rhs.d); return result; } result는 다른 객체처럼 생성자를 통해 생성된다. 그리고 result는 지역 변수이므로 operator*가 끝난 시점에 소멸된다. 즉, result는 이미 소멸된 메모리를 가리킨다. 2. 힙 const Rational& operator*(const Rational& lhs, const Rational& ..
[Effective C++] 20. 값에 의한 전달보다는 상수객체 참조자에 의한 전달 방식을 택하는 편이 대개 낫다
[Effective C++] 20. 값에 의한 전달보다는 상수객체 참조자에 의한 전달 방식을 택하는 편이 대개 낫다
2022.05.29값에 의한 전달 방식 기본적으로 C++은 함수로부터 객체를 전달받거나 함수에 객체를 전달할 때 값에 의한 전달(pass-by-value) 방식을 사용한다. 특별히 다른 방식을 지정하지 않은 함수 매개변수는 실제 인자의 사본을 통해 초기화되며, 어떤 함수를 호출한 쪽은 그 함수가 반환한 값의 사본을 돌려받는다. 이들 사본을 만들어 내는 것은 복사 생성자인데, 이 점 때문에 값에 의한 전달이 고비용의 연산이 되기도 한다. class Person { public: Person(); virtual ~Person(); ... private: std::string name; std::string address; }; class Student: public Person { public: Student(); ~Stud..
[Effective C++] 19. 클래스 설계는 타입 설계와 똑같이 취급하자
[Effective C++] 19. 클래스 설계는 타입 설계와 똑같이 취급하자
2022.05.29새로운 클래스를 정의한다는 것은 새로운 타입을 하나 정의하는 것과 같다. 효과적인 클래스 설계를 위한 질문들 1. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가? 이 부분에 따라 클래스의 생성자와 소멸자의 설계가 바뀐다. 메모리 할당 함수(operator new, operator new [], operator delete, operator delete [])를 직접 작성할 경우 설계에 영향을 미친다. 2. 객체 초기화는 객체 대입과 어떻게 달라야 하는가? 생성자와 대입 연산자의 동작 및 둘 사이의 차이점을 결정짓는다. 초기화와 대입은 각각에 해당되는 함수 호출이 다르므로 헷갈리지 않는 것이 가장 중요하다. 3. 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것..
[Effective C++] 18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자
[Effective C++] 18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자
2022.05.29제대로 쓰기엔 쉽고 엉터리로 쓰기엔 어려운 인터페이스를 개발하려면 우선 사용자가 저지를 만한 실수의 종류를 머리에 넣어두고 있어야 한다. 사용자 타입 시스템을 활용한 인터페이스 아래 예는 날짜를 나타내는 어떤 클래스에 넣을 생성자를 설계하는 과정이다. class Date { public: Date(int month, int day, int year); ... }; 문제점 1. 매개변수의 전달 순서가 잘못될 여지가 있다. Date d(30, 3, 2022); // 일과 월을 바꿔서 입력함 2. 월과 일에 해당하는 숫자가 잘못된 숫자일 수 있다. Date d(3, 40, 2022); 3월 40일은 없다. 해결방안 1. 새로운 타입을 들여와 인터페이스를 강화하여 사용자의 실수를 막는다. struct Day {..
[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*)으로 변환할 방법이..