1. 개요
블로그의 주인장인 여울은 게임을 사랑하는 개발자이다.
여울은 게임을 소개하는 프로그램을 만들었데 이 프로그램에는 게임을 나타내는 클래스가 있다.
class Game {};
그런데 이세상에 똑같은 게임이 존재할까요?
아닙니다.
똑같은 게임은 없고 모든 게임은 각자의 이름이 있고 각자의 플레이 방식이 있습니다.
즉 Game객체는 복사본을 만드는 것이 이치에 맞지 않습니다.
그래서 Game이랑 객체 자체를 복사시도를 하면 컴파일이 되지 않았으면 합니다.
Game MapleStory;
Game BattleGround;
Game StarCraft(MapleStory); // 복사를 막아주세요!!
MapleStory = BattleGround; // 복사를 막아주세요!!
프로그래머가 어떠한 기능이 수행되기를 바라지 않는다면 해야하는 것은 그 기능을 수행하는
함수 자를 만들지 않는 것입니다.
그러나 복사 생성자와 복사대입 생성자에게는 소용없는 일입니다.
왜냐하면 컴파일러가 필요에 따라서 자동으로 만들어주기 때문입니다.
2. 복사 막기
그렇다면 어떻게 이런 문제를 해결할 수 있을까요?
2-1. private로 선언
핵심은 바로 컴파일러가 자동으로 생성하는 함수는 모두 public이라는 사실입니다.
그러니까 우리는 애초에 private로 복사 생성자와 복사 대입 연산를 선언해버리면 됩니다.
이렇게 선언이 되었음으로 컴파일러는 자동으로 public으로 된 생성자를 만들지 않고
또한 private이기 때문에 외부로부터의 호출을 차단하는 효과도 가지게 됩니다.
그러나 여기까지만 한다면 뚫리는 부분이 생기게 됩니다.
그것은 바로 friend함수의 호출 가능성입니다.
그래서 이것을 막기 위해서 선언만 하고 정의는 하지 않는 방법이 추가로 있습니다.
2-2. 정의 안하기
class Game
{
public:
//
private:
Game(const Game&);
Game& operator=(const Game&);
}
매개 변수 이름이 빠져있어도 잘됩니다.
이상태에서 복사를 시도하려고 하면 컴파일러가 오류를 뱉을 것이고
실수로 friend함수에서 실행하려고 하는것도 막을 것입니다.
그러나 이 방식은 컴파일은 통과하지만 링커 단계에서 오류가 발생합니다.
오류는 컴파일 단계에서 빨리 잡는 것이 좋습니다
2-3. 클래스 만들어서 상속받기
class Uncopyable {
protected:
// 생성과 소멸은 파생 클래스에서 가능하도록 protected
Uncopyable() {}
~Uncopyable() {}
private:
// 복사 생성자와 복사 대입 연산자는 private으로 선언하고 정의하지 않음
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class Game : private Uncopyable {
private:
std::string name;
public:
Game(const std::string& gameName) : name(gameName) {}
// 복사 생성자와 복사 대입 연산자를 선언할 필요 없음
// Uncopyable에서 상속받은 private 멤버 때문에 자동 생성되지 않음
};
Game객체의 복사를 시도하면 전부 막을 수 있게 됩니다.
상속은 public일 필요도 없고 소멸자도 가상 소멸자가 아니어도 됩니다.
Game객체의 복사를 시도하려고 하면 Game 클래스의 복사 생성자와 복사 대입 연산자를 만들려고 할 것입니다.
그리고 그것은 부모 클래스의 버전을 따라가게 되어있는데 애초에 private이므로 이런일은 일어나지 않습니다.
항목39의 공백 기본 클래스 최적과 기법이 막힐 여지도 있다.
그러나 Uncopyable 클래스는 기본 클래스여서 이 기법을 쓰면 다중 상속이 될 가능성도 있다.
다중 상속시에는 공백 기본 클래스 최적화가 돌아가지 못할때도 있습니다.
이런건 무시하고 해도 상관없습니다. 결국 돌아가기 때문이죠
취소선 부분은 추후에 다시 공부하고 오도록 하겠습니다.
3. delete
그러나 사실이것은 98버전 C++에서 사용되었던 방법들이고
C++ 11부터는 이런식으로 delete를 이용해서 삭제가 가능합니다.
class Game {
private:
std::string name;
public:
Game(const std::string& gameName) : name(gameName) {}
// 복사 생성자와 복사 대입 연산자를 명시적으로 삭제
Game(const Game&) = delete;
Game& operator=(const Game&) = delete;
};
delete를 쓰는 방법의 장점을 소개하자면
- 컴파일 시점에 오류를 발견한다는 점
- 의도가 코드에서 명확하게 드러난다는 점
- 특정 인수 타입에 대해서만 함수를 삭제할 수 있다는 유연성
- friend 함수에도 적용된다는 점
이렇습니다.
이것만은 잊지 말자!
- 컴파일러에서 자동으로 제공하는 기능을 허용치 않으려면, 대응되는 멤버 함수를 private로 선언한 후에 구현은 하지 않은 채로 두십시오 Uncopyable과 비슷한 기본 클래스를 쓰는 것도 한 방법입니다.
그냥 delete를 쓰도록 하자.
옛날 코드를 읽을때 도움이 될법한 항목이었다.
c++11에서는 대입은 막고 이동연산은 가능하게 할 수도있다!
항목 7
https://yeoul0714.tistory.com/33
[Effective C++] 항목 7: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자
1. 부모 클래스 소멸자를 virtual로 해야하는 이유우리는 기본 클래스의 소멸자는 반드시 virtual로 선언해주어야 합니다. 아래 예시를 통해서 그 이유를 알아보도록 합시다.// Effective C++ 항목 7: 다
yeoul0714.tistory.com
'Effective C++ > Chapter 2: 생성자, 소멸자 및 대입 연산자' 카테고리의 다른 글
[Effective C++] 항목 10: 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2025.04.23 |
---|---|
[Effective C++] 항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2025.04.21 |
[Effective C++] 항목 8: 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2025.04.19 |
[Effective C++] 항목 7: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2025.04.18 |
[Effective C++] 항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2025.04.18 |