목차

     

     


     

     

    Chapter 1:  C++에 왔으면 C++의 법을 따릅시다


     

     

    항목 1:  C++ 언어들의 연합체로 바라보는 안목은 필수

     

    포인터는 메모리의 스택 영역에 있고 포인터가 가리키는 객체는 힙에 있다!


     

     

    항목 2:  #define을 쓰려거든 const, enum, inline을 떠올리자

     

    Q. #define 매크로의 유효 범위는?

    더보기

    A. 컴파일이 끝날 때까지다.


     

     

    항목 3:  낌새만 보이면 const를 들이대 보자!  

     

    Q. 아래의 코드에서 값을 변경하지 못하도록 const를 붙이고 싶습니다. 어디에다가 const를 붙이는게 좋을까요?

    char* authorName = "Scott Meyers";

     

    더보기

    A. 값이 변경되지 않도록 앞쪽에 const를 붙이면 된다. 하지만 주소값도 변경되지 않게 const를 하나 더 붙이는게 좋다.

    const char* authorName = "Scott Meyers"; 		// 값 변경x
    const char* const authorName = "Scott Meyers";  // 값 변경x + 주소값 변경x

     

     

    Q. mutable 키워드는 어떤 역할을 할까요?

    더보기

    A. mutable 키워드는 비트 수준의 상수성를 풀어주기 위해서 사용하는 키워드입니다.

    • C++이 만들어내는 기본적인 함수는, 생성자, 소멸자, 복사생성자, 복사 연산자 입니다. 이 4가지 모두 inline 함수로 만들어진다는데 알고 계셨나요? 저는 이 책을 읽고 알게 됐습니다.
    • const_cast 를 사용해서 상수성을 풀어주기 보다, mutable을 사용해서 논리적으로 상수성을 풀어주는 방법도 있다는 사실을 기억하자!

     

     

    Q. 아래 두 멤버 함수는 같은 오버로드일까요 다른 오버로드일까요?

    class MyClass {
    public:
        void A(); //read - write 
        void A() const; //read-only
    };
    더보기

    A. “오버로드된 함수에 대한 호출이 모호합니다”란 문구가 나오며 컴파일 되지 않습니다.

    void A(int a);
    void A(const int a);

     

     

    Q. 아래 상황에서 발생하는 문제는?

    #include <iostream>
    using namespace std;
    
    class TextBlock {
    public:
    	TextBlock(string te) : text(te){}
    
    public:
    	char operator[] (std::size_t position) { return text[position]; }
    
    private:
    	std::string text;
    };
    
    int main() {
    	const TextBlock tb("Hello");
    	tb[0] = 'x';
    
    	return 0;
    }
    
    더보기

    A. tb[0] = ‘x’ 컴파일 에러 발생합니다. 
    기본제공 타입을 반환하는 함수의 반환 값을 수정하는 일은 절대로 있을 수 없기 때문입니다.
    반환 시 ‘값에 의한 반환’을 수행하는 C++의 성질 상 수정되는 값은 tb.text[0]의 사본이지 tb.text[0] 자체가 아닙니다.

     

    “==로 생각하고 친 건데 실수로 =를 쳤네”와 비슷한 컴파일 에러를 보게 될 일이 없을겁니다.

    레퍼런스를 쓰는데 안전하게 넘기기 위해 const랑 같이 사용합니다.

     

    위와 같이 컴파일 에러가 나지 않도록 코드를 고치면 아래와 같습니다.

    #include <iostream>
    using namespace std;
    
    class TextBlock {
    public:
    	TextBlock(string te) : text(te){}
    
    public:
    	char& operator[] (std::size_t position) { return text[position]; }
    	
    private:
    	std::string text;
    };
    
    int main() {
    	TextBlock tb("Hello");
    	tb[0] = 'x';
    
    	return 0;
    }

     

     

    항목 4:  객체를 사용하기 전에 반드시 그 객체를 초기화하자

     

    객체를 구성하는 데이터의 초기화 순서

    1.  기본 클래스는 파생 클래스보다 먼저 초기화한다.
    2.  클래스 데이터 맴버는 그들이 선언된 순서대로 초기화된다.

     


    Q. 비지역 정적 객체들의 초기화 순서 문제를 어떻게 해결할까요?

    더보기

    별개의 번역 단위에서 정의된 비지역 정적 객체들의 초기화 순서는 '정해저 있지 않습니다.'

     

    비지역 정적 객체들의 초기화 순서 문제를 해결하기 위해서 비지역 정적 객체를 지역 정적 객체로 바꾸는 방법이 있습니다. 함수 속에 넣어서 정적 객체로 선언하고 이들에 대한 참조자를 반환하게 합니다. 비지역 정적 객체를 직접 참조하는 상황을 없애고 함수 호출로 대신하여 문제를 해결할 수 있습다.

     

     

    Q. 다음 중 옳은 것은?
    1. 클래스는 선언과 동시에 멤버 변수가 초기화된다. 구조체도 선언과 동시에 멤버 변수가 초기화된다. 
    2. 클래스는 선언과 동시에 멤버 변수가 초기화된다. 구조체는 선언과 동시에 멤버 변수가 초기화 되지 않는다.
    3. 클래스는 선언과 동시에 멤버 변수가 초기화되지 않는다. 구조체는 선언과 동시에 멤버 변수가 초기화된다.  
    4. 클래스는 선언과 동시에 멤버 변수가 초기화되지 않는다. 구조체도 선언과 동시에 멤버 변수가 초기화 되지 않는다.

    더보기

    4번. 클래스와 구조체는 선언과 동시에 자동으로 멤버 변수가 초기화되지 않습니다.

     

     

    Q. 생성자에 멤버 초기화 리스트를 사용하는 것이 값을 대입하는것 보다 좋은 이유는 뭘까요?

    더보기

    멤버 초기화 리스트는 복사 생성자 한 번 호출하는 반면 값을 대입하면 기본 생성자 호출 후에 복사 대입 연산자를 연달아 호출합니다. 따라서 복사 생성자를 한 번 호출하는 방법이 더 효율적입니다. 

     

     

    Q. 아래의 코드를 읽고 각각 생성인지 대입인지 설명해보세요.

    Pos pos1(10);  // 복사 생성자 
    Pos pos2 = 20; //  생성자인가 복사 생성자인가?
    
    Pos pos3; // 기본 생성자 
    pos3 = 2; // 복사 대입 연산자로 대입
    더보기

    둘 다 대입이 아니라 생성하는 것입니다.

    왜? 선언과 동시에 초기화를 해주었으므로.

    대입이라는 것은 결국, 객체들이 다 만들어진 후에 값을 넣어주는 것입니다.

    초기화 리스트 쓰면 복사 생성자.

     

     

    Q. 디자인 패턴 중 하나인 싱글톤 패턴의 문제점은 멀티플레이에서 나옵니다. 어떤 문제가 발생할 수 있을까요? 

    더보기

    A. 

    • 플레이어가 여러명인데 각각의 다른정보를 가져올 때 공통된 값을 가져오는 경우
    • 경쟁상태에 빠져서 교착상태에 빠진다. → 싱글톤 패턴 때문일까? ⇒ 그렇습니다!
    • static 키워드가 붙어서 사용합니다.

    싱글톤 패턴은 하나의 인스턴스만을 생성하도록 하는 디자인 패턴입니다. 이는 많은 경우에 유용하게 사용될 수 있지만, 멀티스레드 환경에서 사용될 때 문제가 발생할 수 있습니다. 멀티스레드 환경에서의 싱글톤 패턴의 문제점은 다음과 같습니다:

    1. Race Condition

    • 멀티스레드 환경에서 여러 스레드가 동시에 싱글톤 인스턴스를 생성하려고 할 때, 경쟁 조건이 발생할 수 있습니다. 두 스레드가 동시에 인스턴스의 존재 여부를 확인하고 없다고 판단한 후에 인스턴스를 생성하는 경우가 생길 수 있습니다.

    2. 지연 초기화의 안전성 문제

    • 싱글톤 인스턴스가 필요한 시점에 생성되는 "지연 초기화" 방식을 사용하는 경우, 여러 스레드가 동시에 해당 인스턴스를 생성하려고 할 때 안전한지 보장하기 어려울 수 있습니다.

    3. 메모리 가시성 문제 

    • 멀티코어 프로세서에서는 각 코어가 자체 캐시를 가질 수 있으며, 이로 인해 메모리의 변경 사항이 다른 코어에 즉시 반영되지 않을 수 있습니다. 이러한 메모리 가시성 문제로 인해 한 스레드에서 싱글톤 인스턴스를 수정한 내용이 다른 스레드에서 올바르게 보이지 않을 수 있습니다.

    4. 해결 방안의 복잡성 

    • 위의 문제를 해결하기 위해서는 추가적인 동기화 메커니즘이나 스레드 세이프한 초기화 방법이 필요합니다. 이로 인해 코드가 복잡해지고 성능에 영향을 줄 수 있습니다.


    이러한 문제를 해결하기 위해서는 싱글톤 패턴을 구현할 때 스레드 안전성을 고려하여야 합니다. 이를 위해서는 스레드 안전한 초기화 방법을 사용하거나, 미리 인스턴스를 생성하여 초기화하는 등의 방법을 사용할 수 있습니다.