반응형
반응형

0. 요약

  1. 값에 의한 전달 방식이 유리한 경우
    • 기본 자료형(int 등)
    • STL 반복자
    • STL 함수 객체 타입
  2. 위의 경우를 제외하고 모두 상수 객체에 대한 참조자로 전달하자
    • 생성자/소멸자를 호출하지 않기 때문에 효율적
    • const를 사용하여 전달되는 객체가 변경되지 않음을 보장
    • 복사 손실 문제가 없어짐

1. 값에 의한 전달 방식

  1. 함수로부터 객체를 전달 받거나, 함수에 객체를 전달할 때 값에 의한 전달 방식을 사용

    • 함수 매개 변수는 실제 인자의 '사본'을 통해 초기화
    • 함수 호출 시 : 함수가 반환한 값의 '사본'을 전달 받음
    • 이들 사본을 만들어내는 것은 복사 생성자
  2. 복사 생성자는 고비용 연산!

    • 아래 예제 참고
    • plato를 인자로 전달하면 매개 변수 s를 초기화 하기 위해 student의 복사 생성자 호출!
    • validateStudent 함수가 끝나면 Student의 소멸자 호출!
    • Student에는 총 4개의 string이 있기 때문에 4개의 생성/소멸자 호출됨
class Person{
public:
   Person();
   virtual ~Person();
private:
   std::string name;
   std::string address;
};

class Student: public Person{
public:
   Student();
   ~Student();
private:
   std::string schoolName;
   std::string schoolAddres;
};

bool validateStudent(Student s);
Student plato;
bool platoIsOk = validateStudent(plato);

2. 상수객체에 대한 참조자로 전달

bool validateStudent(const Student& s);
  1. 새로운 객체가 만들어지지 않는다.
    • 생성자/소멸자가 호출되지 않는다.
  2. const를 사용
    • Student 객체 s는 변경되지 않음을 보장한다!
  3. 복사 손실 문제가 없어지는 장점
    • 상속받은 객체 생성 시 부모 객체의 멤버만 있는 문제
    • 참조자는 보통 포인터를 써서 구현됨(C++ 컴파일러 동작 원리)
    • 즉, 참조자를 전달한다는 것은 포인터를 전달한다는 것
class Window {
public:
   std::string name() const;
   virtual void display() const;
};

class WindowWithScrollBars: public Window{
public:
   virtual void display() const;
};

void printNameAndDisplay(Window w){
   std::cout << w.name();
   w.display(); // Window의 display 함수만 호출
}

// 위 함수에 WindowWithScrollBars 객체를 전달
WindowWithScrollBars wwsb;
// Window 객체 부분만 있고,
// WindowWithScrollBars 부분은 초기화 되지 않음
printNameAndDisplay(wwsb);

// 상수 객체 참조자로 전달하여 문제 해결
// 어떤 종류의 Window가 전달되더라도 복사 손실 문제 없어짐
void printNameAndDisplay(const Window& w){
   std::cout<< w.name();
   w.display();   // 전달되는 타입에 따라 다른 display 호출됨
}

3. 값에 의한 전달이 상수 객체에 대한 참조자 전달보다 효율적인 때도 있다

  1. 기본 자료형(int 등)일 경우
  2. STL의 반복자와 함수 객체의 경우
    • 반복자와 함수 객체 구현시 반드시 아래 2가지 고려하여 만들어야함
    • 복사 효율을 높여야함
    • 복사 손실 문제에 노출되지 않도록 해야함

4. 사용자 타입은 가급적 참조자 전달을 사용하자

  1. 위에서 언급한 상황을 제외하고는 참조자 전달을 사용하자!
  2. 타입의 크기가 작으면 값에 의한 전달이 효율적일까?
    • 꼭 그렇지는 않다.
    • 멤버 데이터로 포인터 변수 하나만 가지고 있더라도, 복사 시에 해당 포인터 값을 모두 복사하기 때문에(깊은 복사) 비효율적일 수 있다.
  3. 지금은 크기가 작더라도 나중에 커질 수 있다.

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. 클래스 설계는 타입 설계와 같다
    • 아래 내용들을 깊게 고민하고 질문하자

1. 클래스 설계는 새로운 타입을 정의한다고 생각하자

  1. 함수와 연산자를 오버로딩
  2. 메모리 할당 및 해제를 제어
  3. 객체 초기화 및 종료를 처리하는 작업 정의

2. 효과적인 클래스 설계를 위한 질문들

  1. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?

    • 이 부분에 따라 클래스 생성자/소멸자의 설계가 바뀜
    • 메모리 할당 함수를 직접 작성할 경우 설계에 영향을 미침
  2. 객체 초기화는 객체 대입과 어떻게 달라야 하는가?

    • 생성자와 대입 연산자의 동작 및 둘 사이의 차이점을 결정
    • 초기화와 대입을 헷갈리지 말자!
  3. 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가?

    • 값에 의한 전달을 구현하는 쪽은 복사 생성자라는 점을 기억하자
  4. 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?

    • 클래스 데이터 멤버 몇 가지 조합은 반드시 유효해야함
    • 이런 조합을 불변속성이라고 하며, 클래스 차원에서 지켜줘야함
    • 불변 속성에 따라 멤버 함수 안에서 해야할 에러 점검 루틴 결정
    • 불변 속성에 따라 생성자, 대입 연산자, setter 함수 결정
    • 불변 속성은 함수가 발생시키는 예외에도 영향을 미침
  5. 기존의 클래스 상속 계통망에 맞출 것인가?

    • 이미 존재하는 클래스를 상속받아 설계시 이들 클래스에 의해 제약을 받음
    • 멤버 함수의 가상 여부가 가장 큰 요인
    • 내가 만든 클래스를 다른 클래스에서 상속받을 수 있게 한다면, 가상 함수 여부 결정이 필요함
  6. 어떤 종류의 타입 변환을 허용할 것인가?

    • 새로운 클래스와 다른 타입간의 변환의 정의가 있어야함
    • 암시적으로 변환하길 원한다면, 타입 연산자 오버로딩 필요
    • 아니면, non-explicit 생성자를 T2 클래스에 넣어야함
    • 명싲거으로 변환하길 원한다면, 변환을 맡는 함수를 만들자
    • 이때 타입 변환 연산자 또는 non-explicit 생성자를 만들지 말자
  7. 어떤 연산자와 함수를 두어야 의미가 있을까?

    • 클래스 안에 선언할 함수가 여기서 결정
  8. 표준 함수들 중 어떤 것을 허용하지 말 것인가?

    • private로 선언해야 하는 함수를 결정
  9. 새로운 타입의 멤버에 대한 접근권한을 어느 쪽에 줄 것인가?

    • 클래스 멤버에 대한 public, protected, private 영역 결정
    • friend로 만들어야할 클래스 및 함수를 결정
  10. 선언되지 않은 인터페이스로 무엇을 둘 것인가?

    • 만들 타입이 제공할 보장이 어떤 종류인가에 대한 질문
    • 보장할 수 있는 부분은 수행 성능 및 예외 안전성 그리고 자원 사용
    • 보장하겠다고 결정한 결과는 클래스 구현에 있어 제약으로 작용
  11. 새로 만드는 타입이 얼마나 일반적인가?

    • 새로 만드는 것은 하나의 타입이 아니라 동일 계열의 타입군일 수 있다.
    • 그렇다면, 새로운 클래스를 만드는 것이 아니라, 새로운 클래스 템플릿을 정의해야 한다!
  12. 정말로 꼭 필요한 타입인가?

    • 기존의 클래스에서 기능 몇 개가 아쉽다면, 상속 받지 마라
    • 간단하게 비멤버 함수 또는 템플릿을 정의하자!

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. 인터페이스를 잘못 사용하지 못하도록 고민하자!!
    • 사용자가 저지를만한 실수를 생각하자
    • 새로운 타입을 들이고, 제약을 넣어 실수를 방지하자
  2. 특별한 이유가 없다면 기본 타입처럼 사용자 타입을 구현하자
  3. 일관성 있는 인터페이스를 제공하자
  4. 스마트 포인터를 써서 사용자 오류를 방지하자

1. 인터페이스를 잘 못 사용하지 못하도록 하자

  1. 사용자가 저지를만한 실수를 미리 생각하자!
    • ex) 날짜를 나타내는 클래스
    • 매개 변수를 잘 못 넣을 수 있음
class Date{
public:
  Date(int month, int day, int year);
};

Date d(30, 3, 1995) ; // 3과 30을 잘못씀
Date d(3, 40, 1995); // 30을 넣어야했는데 오타로 40..
  1. 새로운 타입을 들여와 인터페이스를 강화하자!
    • 일, 월, 연을 구분하는 랩퍼 타입을 만들자.
    • 이 타입을 Date 생성자 안에 두자.
// 일 타입
struct Day{
   explicit Day(int d) : val(d) {}
   int val;
};

// 월 타입
struct Month{
   explicit Month(int m) : val(m) {}
   int val;
}

// 년 타입
struct Year {
   explicit Year(int y) : val(y) {}
   int val;
}

// Date 클래스 재정의
class Date{
public:
  Date(const Month& m, const Day& d, const Year& y);
};
Date d(Month(3), Day(30), Year(1995));
  1. 새로운 타입에 제약을 넣어 오류를 방지하자!
    • 월은 12개 값만 가지므로, 제약을 주자
class Month{
public:
   static Month Jan() {return Month(1);}
   ...
   static Month Dec() {return Month(12);}
private:
  explicit Month(int m);
};

Date d(Month::Mar(), Day(30), Year(1995));
  1. 특별한 이유가 없다면, 사용자 정의 타입은 기본 제공 타입처럼 동작하게 하자!

    • 항목 3의 if(a*b = c) 의 예제 참고
    • operator*의 반환타입을 const로 한정하여, 사용자 실수로 인한 c 대입 방지 가능
  2. 일관성 있는 인터페이스를 제공하자

    • STL 컨테이너는 대체로 일관성이 있어서 사용하기 편리하다.
    • ex) STL 컨테이너는 size란 멤버 함수를 공통적으로 제공한다.
    • length, size, count 등 혼용하면 헷갈린다.

2. 스마트 포인터 사용

  1. 사용자가 신경쓰지 않도록 하자(외우지 않도록)
    • Factory 함수에서 포인터를 반환할 때 shared_ptr을 반환하여 메모리 누수 문제를 해결하자.
Investment* createInvestment(); // (X)
std::shared_ptr<Investment> createInvestment(); // (O)
  1. 스마트 포인터의 삭제자를 활용하자
    • Investment* 포인터를 직접 삭제하지 않고, 별도 삭제자를 통해 삭제하자
    • 하지만 별도 삭제자를 사용하지 않고 직접 delete를 할 가능성이 있다.
    • 또한 실수로 delete를 하고, 삭제자도 호출 할 수도 있다.
    • 아래와 같이 create할 때 삭제자가 포함된 shared_ptr을 반환하도록 구현
std::shared_ptr<Investment> createInvestment(){
// 방법 1. 포인터를 null ptr로 초기화 하고 나중에 대입하는 방석
   std::shared<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment);

// 방법 2. 실제 객체 포인터를 바로 생성자에 넘기는 방법
   std::shared_ptr<Investment> retVal(new Investment, getRidOfInvestment);

   retVal = ...;
   return retVal;
}
  1. shared_ptr은 '교차DLL 문제' 를 방지한다.
    • 교차 DLL 문제 : 어떤 DLL에서 객체를 생성하고, 다른 DLL에서 소멸하는 문제
    • 객체 생성/삭제가 서로 다른 DLL에서 호출될 때 발생
    • shared_ptr은 생성된 DLL과 동일한 DLL에서 delete를 사용하도록 삭제자가 만들어져 있어서 방지 가능
// 아래 함수가 반환하는 shared_ptr은 다른 DLL 사이에 이리저리 넘겨져도 교차 DLL 문제를 걱정하지 않아도 된다.
std::shareD_ptr<Investment> createInvestment(){
   return std::shared_ptr<Investment>(new Stock);
}

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. new로 생성한 객체를 스마트포인터에 저장하는 코드를 별도의 한 문장으로 만들자
    • shared_ptr pw(new xxx);
  2. 이유
    • new 실행과 해당 자원을 스마트포인터 생성자로 전달하는 사이에 예외 발생시 자원 누수 발생!

1. 문제 사항

  1. 가정
    • 처리 우선 순위를 알려주는 함수 존재
    • 동적 할당한 Widget 객체에 대해 어떤 우선순위에 따라 처리를 적요하는 함수 존재
int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);

// processWidget을 사용
processWidget(new Widget, proiority());
  1. 위의 processWidget() 함수가 동작할까?
    • shared_ptr의 생성자는 explicit로 선언되어 있음
    • new Widget으로 만들어진 포인터가 shared_ptr 타입으로 암시적 변환이 되지 않음!
    • 아래와 같이 명시적으로 생성 후 호출하도록 변경
processWidget(std::shared_ptr<Widget>(new Widget), priority());
  1. 하지만 위의 문장은 자원이 누수될 가능성이 존재!
    • 위의 한 문장을 수행전 매개 변수 인자를 평가하는 순서 가짐
    • 본 예시에서는 3가지 단계를 거침
    • "new Widget" 을 실행 => shared_ptr 생성자를 호출 => priority() 함수 호출
    • 문제는 3개의 단계의 순서가 컴파일러 마다 다름!
  2. 만약 호출 순서가 아래와 같고, priority 에서 예외가 발생했다면?
    • "new Widget" 실행
    • priority() 실행
    • shared_ptr 생성자 호출
  3. new Widget으로 할당된 메모리가 해제되지 않아 누수 발생!

2. 해결 방법

  1. Widget를 생성해서 RAII에 저장하는 코드를 별도 한 문장으로 만들자!
  2. 그리고, 나머지 문장을 실행하자!
std::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. new와 delete 형태를 맞추자
    • new 에 []를 썼으면 delete에 []를 쓰자
    • new 에 []를 안썼으면 delete에 []를 쓰지 말자

1. new, delete의 동작 순서

  1. new

    • 메모리가 할당됨
    • 할당된 메모리에 한 개 이상의 생성자가 호출됨
  2. delete

    • 할당된 메모리에 한 개 이상의 소멸자가 호출됨
    • 메모리가 해제됨

2. 객체 1개 vs 배열로 할당/해제

  1. 한 개 객체 할당 시 메모리 구조
    • object
  2. 객체 배열 할당 시 메모리 구조
    • 객체 수(n) - object - obejct - ...
    • 위의 구조로 delete 연산자가 몇 번 호출 될지 쉽게 알 수 있음
  3. 주의 사항
    • delete 뒤에 [] 를 붙여줘야만 포인터가 배열을 가리키고 있다고 이해함
    • 만약 [] 가 없으면 단일 객체로 간주하고 1개의 객체만 delete 처리함

3. 결론

  1. new 와 delete 형태를 맞추자
    • new 에 []를 썼으면 delete에 []를 쓰자
    • new 에 []를 안썼으면 delete에 []를 쓰지 말자
  2. 배열 타입을 typedef으로 만들지 않도록 하자
    • string 또는 vector type을 활용하자!

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. RAII 클래스가 관리하는 실제 자원을 반환하는 방법을 고민하자!
  2. 명시적으로 자원을 반환하자
    • 별도 함수 또는 연산자 오버로딩을 통해 자원을 반환
  3. 암시적으로 자원을 반환하자
    • 형변환 연산자 오버로딩을 통해 자원을 반환

1. RAII클래스가 관리하는 실제 자원을 반환하는 방법 필요

  1. 아래 예제에서 pInv는 shared_ptr 자료형이기 때문에 dayHeld에 인자로 사용 못함
  2. pInv에서 관리하는 실제 자원을 반환해야 사용가능하다!
int dayHeld(const Investment *pi); // 투자 이후 경과일 수
std::shared_ptr<Invesment> pInv(createInvestment()); // 자원 획득

int days = dayHeld(pInv) // => 실패!!

2. 자원을 반환하는 방법

  1. 명시적 변환(explicit conversion) 방법
    • 별도 함수(get)을 통해 자원을 반환 하자!
    • operator 오버로딩(->, * 등)을 통해 자원을 반환하자
// 1. 함수를 통해 자원 반환
int days = dayHeld(pInv.get());

// 2. 연산자 오버로딩을 통해 자원 반환
class Invesment{
public:
  bool isTaxFree() const;
}

// 2.1 -> 연산자 오버로딩
Investment* createInvestment();
std::shared_ptr<Investment> pi1(createInvestment());
bool taxable1 = !(pi1->isTaxFree());

// 2.2 * 연산자 오버로딩
std::shared_ptr<Invetsment> pi2(createInvestment();
bool taxable2 = !((*pi2).isTaxFree());
  1. 암시적 변환(implicit conversion) 방법
    • 암시적 형변환 oeprator를 통해 자원을 반환!
// C-API
FontHandle getFont();
void releaseFont(FontHandle fh);

// RAII  클래스
class Font{
public:
  explicit Font(FontHandle fh) : f(fh) {}
  ~Font() { releaseFont(f)}; }

  // 암시적 변환 함수(형 변환)
  operator FontHandle() const { return f; }
private:
  FontHandle f;
};

// 함수
void changeFontSize(FontHandle f , int newSize);

// 암시적 변환 사용
Font f(getFont());
int newFontSize;
changeFontSize(f, newFontSize); // Font -> FontHandle로 암시적 변환 수행

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. 자원 관리 클래스(RAII 클래스) 만들 때 복사에 대해 고민하자.
  2. 복사에 대한 선택지는 아래와 같다.
    • 복사를 금한다(Mutex)
    • 관리하는 자원의 참조 카운팅 수행(shared_ptr)
    • 관리하는 자원을 복사(string)
    • 관리하는 자원의 소유권을 이전(unique_ptr)

1.뮤텍스 관리 객체 예시

  1. 뮤텍스 잠금을 관리하는 객체 구현
class Lock{
public:
   explicit Lock(Mutex *pm) : mutexPtr(pm){
      lock(mutexPtr);
   }
   ~Lock(){
      unlock(mutexPtr);
   }
private:
   Mutex *mutexPtr;
}
  1. 사용은 RAII 방식으로
Mutex m;
{
   Lock m1(&m); // lock
} // 블록이 끝나면 unlock
  1. 만약 Lock객체가 복사 된다면?
    • 사본이 의미가 없기 때문에 복사가 되면 안된다.

2. RAII 객체 복사 구현시 선택지

  1. 복사를 금지(3.참고)
  2. 관리하는 자원의 참조 카운팅 수행(4.참고)
  3. 관리하는 자원을 복사함(5.참고)
  4. 관리하는 자원의 소유권을 이전함(6.참고)

3. 복사를 금지

  1. RAII 객체가 복사 되면 안되는 경우 금지
    • 위의 Mutex의 경우에서 사용
  2. 복사 생성자를 금지

4. 관리하는 자원의 참조 카운팅 수행

  1. 자원을 사용중인 마지막 객체가 소멸될 때 까지 유지해야할 경우(shared_ptr의 경우) 사용
  2. 방법
    • 복사 시에 자원을 참조하는 객체의 개수 카운트를 증가
    • 참조 객체 수가 0이되면 자원을 해제하도록 구현
  3. shared_ptr을 이용하는 방법
    • 참조 카운팅 구현하려면 멤버 변수를 shared_ptr로 구현하면 됨
  4. shared_ptr의 삭제자
    • 삭제자(deleter) : 참조 카운트가 0이 되었을 때 호출되는 함수 또는 함수 객체
    • shared_ptr은 삭제자 지정(변경)을 허용함
  5. Mutex 예제에서 shared_ptr을 이용하도록 변경!
    • shard_ptr은 참조 개수가 0이 되면 가리키고 있던 대상을 삭제 하기 때문에 Mutex의 예 맞지 않음(Mutex는 다 썻을 때 잠금 해제만 하면 됨)
    • 이럴 때 shared_ptr의 삭제자를 사용하면 참조 객체가 0이 될 때 Mutex를 unlock하도록 구현할 수 있음
class Lock{
public:
   explicit Lock(Mutex *pm)
      : mutexPtr(pm, unlock) // 삭제자로 unlock을 지정
   {
      lock(mutexPtr.get());
   }
private:
   shared_ptr<Mutex> mutexPtr;
}

Mutex m;
{
   Lock m1(&m); // lock
} // 블록이 끝나면 삭제자(unlock)가 호출

5. 관리하는 자원을 복사함

  1. 복사가 필요할 경우 복사를 지원(string의 경우)
  2. 이 때 반드시 깊은 복사를 수행해야함

6. 관리하는 자원의 소유권을 이전

  1. 자원을 참조하는 RAII 객체를 딱 하나만 만들고 싶을 때 사용(unique_ptr의 경우)

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. 자원 관리 객체에서 자원을 관리하자.(RAII 사용)
  2. 자원 관리 객체는 소멸자에서 해당 자원을 해제 하자!

1. 메모리가 누수될 수 있는 상황 가정

  1. 구현한 내용
    • 투자를 모델링한 기본 클래스 Investment
    • Investment 계열의 클래스를 반환하는 팩토리 함수 createInvestment
  2. 사용부
    • createInvestment를 이용해 Investment 계열 클래스 생성
    • 사용 후 객체 반환(delete)
  3. 문제 사항
    • 삭제 전에 return 문이 있을 경우
    • 삭제 전에 goto문에 의해 루프를 빠져 나갈 경우
    • 삭제 전에 예외가 던져질 경우
// 투자를 모델링한 기본 클래스
class Investment{...};

// Invesment를 상속한 클래스를 return하는 팩토리 함수
Invesment* createInvestment();

void f(){
   Invesment *pInv = createInvestment();
   ...
   delete pInv;
}

2. 해결 방안

  1. 자원을 획득한 후에 자원 관리 객체에게 넘기자!
    • createInvestment를 통해 만든 자원을 auto_ptr에게 넘겨 초기화하는데 사용
    • RAII라고 불림
    • 자원 획득과 자원 관리객체의 초기화가 한문장에서 이루어지는 것이 일상적임
  2. 자원 관리 객체는 자신의 소멸자를 사용해 자원이 확실히 해제되도록 하자!
    • 소멸자는 객체가 소멸될 때 자동으로 호출됨
    • 소멸자가 호출될 때 자원을 해제하면 자동으로 확실히 해제됨
    • 단, 예외가 발생하면 꼬이지만 항목8에서 해결할 예정
  3. 예제(스마트 포인터)
void f(){
   std::unique_ptr<Investment> pInv(createInvesment());
   //책에서는 auto_ptr로 예제를 들었으나, C++11에서 unique_ptr로 대체됨
}

3. 스마트 포인터

  1. unique_ptr

    • 자원에 대해 유일한 소유권을 가짐
    • unique_ptr을 복사 또는 대입하면, 기존 unique_ptr은 null로 변경됨
std::unique_ptr<Invesment> pInv1(createInvesment());
std::unique_ptr<Invesment> pInv2(pInv1);  // pInv1 = null로 할당됨
pInv1 = pInv2;  // pInv2 는 null로 할당됨
  1. shared_ptr
    • 참조 카운팅 방식 스마트 포인터(RCSP:Reference-Counting Smart Pointer)
    • 자원을 가리키는 외부 객체 개수를 확인하다가 0이 되면 자원을 자동으로 삭제함
std::shared_ptr<Investment> pInv1(createInvestment());
std::shared_ptr<Investment> pInv2(pInv1); // pInv1과 pInv2는 같은 객체를 가리킴
pInv1 = pInv2; // 위와 동일ㅈ

참고

  1. Effective C++

별첨

  1. RAII(Resource Acquisition Is Initialization)
    • 생성자에서 리소스를 획득하고 해당 소멸자에서 해제하는 것!
    • 주로 포인터, 뮤텍스 등의 자원의 소멸이 누락되어 발생되는 문제를 방지하기 위해 별도의 자원 관리 객체를 두고, 객체 생성시 자원 획득 및 소멸시 자원 해제하는 것을 얘기함
  2. RAII 클래스 예시
    • string, vector, thread, unique_ptr, shared_ptr, lock_guard, unique_lock, shared_lock
반응형
반응형

0. 요약

  1. 객체 복사 시 모든 데이터 멤버가 빠지지 않도록 하자

  2. 객체 복사 시 모든 기본 클래스 부분이 빠지지 않도록 하자

  3. 복사 생성자와 대입 연산자 구현 시 주의

    • 한쪽을 이용해 다른 한쪽을 구현하려고 하지 말자
    • 필요시 공통된 동작을 제 3의 함수에 분리하자
    • 양쪽에서 이 함수를 호출하도록 구현하자

1. 객체 복사 함수

  1. 객체 복사 함수의 종류
    • 복사 생성자
    • 복사 대입 연산자
  2. 객체 복사 함수 특징
    • 사용자가 만들지 않으면 자동으로 생성된다.
    • 사용자가 만들면 자동으로 생성하지 않는다.
  3. 객체 복사 함수를 만드는 경우 및 주의점
    • 컴파일러가 자동으로 생성해준 복사 함수로는 부족할 때
    • 사용자가 일부 데이터 복사를 누락해도 컴파일러는 알려주지 않는다!

2. 객체 복사 함수 생성 시 주의점

  1. 누락된 멤버 변수가 없도록 해라
    • 누락되어도 컴파일러는 알려주지 않아 부분복사가 될 수 있다.
  2. 상속 받은 클래스의 복사도 누락 없도록 해라
// 복사 생성자
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
   : Customer(rhs),  // 부모 클래스의 복사 생성자 호출
   priority(rhs.priority){}   // 데이터 복사

// 대입 연산자
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
   Custormer::operator=(rhs); // 부모 클래스의 대입 연산자 호출
   priority=rhs.priority; // 데이터 대입
   return *rhis;
}

참고

  1. Effective C++
반응형
반응형

0. 요약

  1. operator= 에서 자기대입에 대한 처리를 반드시 하자
    • 일치성 검사를 통해 자기대입일 경우 처리하지 않도록 하는 방법
    • 본래의 사본을 만들어 할당받는 방법
    • 사본을 swap하는 방법

1. 자기 대입의 예시

  1. 자기 대입이란?
    • 어떤 객체가 자기 자신에 대해 대입연산자를 적용하는 것
class widget{...};
Widget w;
w = w;
  1. 배열 또는 컨테이너 순환 중 자기 대입

    • i와 j가 같다면 자기 대입
a[i] = a[j];
  1. 중복 참조로 인해 자기 대입 발생
    • 중복 참조 : 여러 곳에서 하나의 객체를 참조하는 상태
    • px와 py가 가리키는 대상이 같으면 자기 대입
*px = *py;

2. 자기 대입 문제

  1. 정상적인 경우
    • 대입 연산 시 기존의 pb 포인터 삭제
    • rhs의 pb포인터 값을 재할당 및 복사함
  2. 자기 대입일 경우
    • 대입 연산시 기존의 pb포인터 삭제
    • rhs의 pb포인터도 삭제되어 버림
    • 대입 후에 비어 있는 Bitmap이 할당됨
class Bitmap{...};
class Widget{
public:
   Widget& operator=(const Widget& rhs);
private:
   Bitmap *pb;
};
Widget& Widget::operator=(const Widget& rhs){
   delete pb;
   pb = new Bitmap(*rhs.pb);
   return *this;
}

3. 해결 방법

  1. 대입 연산자에서 일치성 검사 수행
    • 자기 대입이 일어나는 경우는 극히 적음
    • 하지만 대입할 때 마다 일치성 검사를 하므로 효율 떨어짐
    • 또한 new 단계에서 예외 발생 시 삭제된 pb만 남음
Widget& Widget::operator=(const Widget& rhs){
   if (this == &rhs) return *this; // 일치성 검사 수행
   // 이후 과정 동일
   delete pb;
   pb = new Bitmap(*rhs.pb);
   return *this;
}
  1. 삭제 전에 본래의 pb를 복제
    • new 단계에서 예외 발생해도 pb 유지 가능
    • 복제를 통해 자기 대입에 대한 방어도 가능함
Widget& Widget::operator=(const Widget& rhs){
   Bitmap *pOrig = pb; // 원래 pb를 복제
   pb = new Bitmap(*rhs.pb);  // rhs의 pb를 대입
   delete pOrig;  // 원래 pb를 삭제
   return *this;
}
  1. 복사 후 바꾸기 방법(copy and swap)
    • 예외 안정성과 자기대입 안정성을 동시에 가진 operator= 구현 방법
    • 29 항목에서 확인
    • 방법 1과 같이 rhs 사본을 만들어 swap하는 방법
    • 방법 2와 같이 값에 의한 전달 시 사본이 만들어지는 특징을 살려 바로 swap하는 방법
class Widget{
   void swap(Widget& rhs);
};

// 방법 1.
Widget& Widget::operator=(const Widget& rhs){
   Widget temp(rhs); // rhs 사본 만듬
   swap(temp); // *this를 사본과 맞바꿈
   return *this;
}

// 방법 2. 항목 20 참고
Widget& Widget::operator=(Widget rhs){
   swap(rhs);
   return *this;
}

참고

  1. Effective C++
반응형

+ Recent posts

반응형