C++21 항목 15 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 우리가 auto_ptr이나 shared_ptr을 보면 이 스마트 포인터가 관리하는 자원에 외부에서 접근 할 수 있도록 설계 되어있다. std::shared_ptr test(new Test()); test.get(); // Test* 를 반환 test->memberFunction(); // ->로 자원에 접근하여 멤버 함수 호출 가능 (*test).memberFuncion(); // *로 자원에 접근한 후 멤버 함수 호출 가능 자, 그렇다면 우리가 항목 14에서 배운대로 직접 자원관리 클래스를 만들때에도 이러한 점을 고려하는 것이 좋다. 예를 들어 C API에서 글자를 출력하기 위해 제공하는 FontHandle이라는 자원을 관리하는 Font클래스를 직접 만든다고 생각해 보자. FontHandle getFo.. 2024. 1. 19. 항목 14 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 자 우리는 항목 13에서 RAII 기법을 이용해 힙 영역의 자원을 관리하는 방법에 대해 배웠다. 이 예로 우리는 auto_ptr과 shared_ptr에 대해서 배웠는데 경우에 따라서는 우리가 직접 자원 관리 클래스를 만들어야 하는 경우도 생긴다. 아래 예시를 한 번 보자. Mutex 타입의 뮤텍스 객체를 조작하는 C API를 사용하고 있다고 가정하자. 이 C API가 제공하는 함수 중에는 뮤텍스를 잠그는 lock 및 잠금을 푸는 unlock 함수가 있다. void lock(Mutex* pm); void unlock(Mutex* pm); 그런데 나는 여기서 뮤텍스의 잠금을 자동으로 관리해주는 클래스를 하나 만들고 싶다. 그래서 생성시에 자원을 획득하고 소멸시에 자원을 해제하도록 다음과 같이 설계했다. cl.. 2024. 1. 19. 항목 13 자원 관리에는 객체가 그만! 항목 12는 복사 생성자를 만들때 파생 객체의 복사 생성자에서 부모 클래스의 복사 생성자를 호출하여 빠짐없이 복사하자는 내용이었는데 크게 다룰 내용이 없어서 건너 뛰었다. 이번 항목은 자원관리에 객체를 사용하자는 내용인데 우선 아래 코드를 통해 몇가지 가정을 하도록 하자. class Investment { ... }; // 대충 투자 관련 처리를 담당하는 기본 클래스 Investment* createInvestment(); // Investment클래스에서 파생된 객체를 동적할당하여 포인터를 반환하는 팩토리 함수 자 그리고 이 클래스와 함수를 아래 코드처럼 사용한다고 치자. void f() { Investment* pInv = createInvestment(); // 팩토리 함수 호출 ... // pIn.. 2024. 1. 19. 항목 11 operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 항목 10은 대입 연산자에서는 반드시 *this를 반환하라는 내용인데 c++ 기본 문법을 배웠다면 너무나도 당연한 얘기여서 건너 뛰었다. 자 이번 항목은 조금 중요한 내용인데 대입연산자를 만들때 자기 대입에 대한 처리를 해야한다는 것이다. class Widget { ... }; Widget w; ... w = w; // 자기 대입 솔직히 누가 저런식으로 코드를 멍청하게 짜겠느냐 하겠지만 만약 다음과 같은 상황이라면 충분히 가능하다. a[i] = a[j]; *px = *py; 만약 i, j가 같다면, px와 py가 가리키는 곳이 동일하다면 자기 대입은 언제든지 발생할 수 있다. 자, 그런데 자기 대입이 왜 문제가 될까? 솔직히 자기 자신을 대입하면 아무런 일이 발생하지 않을거라고 생각할지도 모른다. 그런데.. 2024. 1. 18. 항목 9 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출 하지 말자 자, 이부분도 상당히 충격을 주었던 부분중 하나인데 우선 제목 그대로 객체의 생성자와 소멸자에서는 절대로 가상 함수를 호출 해서는 안된다. 우선 아래 코드를 보자. class Creature { public: Creature(); virtual void logLife() const = 0; // 타입에 따른 수명을 출력함. ... }; Creature::Creature() { ... logLife(); // 여러가지 작업을 한 후 마지막으로 생명체의 수명을 로깅함. } class Dog : public Creature // 파생 클래스 { public: virtual void logLife() const; // 오버 라이딩 ... }; class Cat : public Creature // 파생 클래스 .. 2024. 1. 18. 항목 8 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 솔직히 개인적으로 이해하기에 머리가 아팠던 부분이다. 우선 아래 코드를 좀 보자. class DBConnection { public: ... static DBConncection create(); void close(); // 데이터 베이스와의 연결을 닫음, 이때 실패시 예외를 던짐. }; 위와 같이 데이터 베이스와 연결을 하고 연결을 종료하는 것을 관리해주는 DBConnection이라는 클래스가 있다고 치자. 그리고 이 클래스는 아래 클래스에서 다음과 같이 사용되고 있다. class DBConn { public: ... ~DBConn() { db.close(); } private: DBConnection db; }; 자 DBConn이라는 클래스는 DBConnection이라는 클래스 객체의 연결과 종료를.. 2024. 1. 18. 항목 7 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 항목 7의 내용은 c++을 공부 했다면 당연히 알고 있어야 하는 내용이지만 몇몇 중요한 내용도 추가된것 같아서 남긴다. 다형성을 가진 기본 클래스에서는 소멸자를 가상소멸자로 선언하자는 것인데 하나의 클래스를 상속시켜서 파생클래스를 만든다고 했을때 반드시 상속의 시초가되는 기본 클래스의 소멸자는 가상함수로 선언을 하라는 말이다. 이유는 가상 소멸자로 선언을 하지 않으면 파생된 최종 클래스의 소멸자만 호출이 되고 그 부모 객체의 소멸자가 호출되지 않기 때문이다. 이 내용은 앞서 말했다시피 C++을 공부했다면 다들 알고 있을 것이다. 자 그런데 여기서 한 가지 놓쳤을만한 이야기를 해볼까 한다. 기본적으로 비어있는 클래스는 1바이트이다. class Parent { public: Parent() = default.. 2024. 1. 18. 항목 6 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 항목 5에 대해서는 글을 쓰지 않았는데 대충 복사 생성자, 대입 연산자같은 함수들은 클래스 설계자가 인위적으로 만들지 않아도 필요에 따라 컴파일러가 만든다는 내용이었다. 이와 같이 컴파일러는 설계자가 만들지 않은 클래스중 필요하다고 생각 되는것은 자신이 직접 생성을 한다. 자, 그러면 여기서 나는 하나의 클래스를 설계하는데 절대로 복사, 대입이 일어나서는 안되는 클래스를 설계한다고 치자. 나는 그래서 일부러 클래스에 복사생성자, 대입연산자를 만들지 않았다. 그런다고 정말 복사가 안될까? 아니다 컴파일러가 직접 생성을 해버린다. class Home { ... }; Home h1; Home h2; Home h3(h1); // 오류가 나기를 바람 h1 = h2; // 오류가 나기를 바람 나는 이렇게 했을 때 .. 2024. 1. 18. 항목 4 객체를 사용하기 전에 반드시 그 객체를 초기화 하자 여기에서 전반적인 내용은 사실 c++ 기본 문법에 관련된 사항이라 내 생각에 좀 중요하다고 생각된 부분만 정리했다. 우선 번역단위 라는 말인데. 번역단위란 간단히 말하면 소스 파일 하나라고 생각하면 된다. 또한 정적 객체란 static, extern으로 선언한 객체인것은 모두 알 것이다. 자 여기서 별개의 번역단위에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져있지 않다라는 것인데. 우선 이 말을 하나하나 풀이 해보면 서로다른 파일에 정의된 비지역 즉, 함수 외부에 선언된 정적 객체들의 초기화 순서는 정해져있지 않다는 뜻이 된다. 이게 문제가 되는 이유는 바로 다음과 같은 경우이다. // filesystem.h class FileSystem { public: ... std::size_t numDis.. 2024. 1. 18. 이전 1 2 3 다음