다른 개발자에게 제공할 코드를 작성할 때, 그 개발자가 코드의 특정 함수를 호출하지 못하게 하는 가장 흔한 방법은 그냥 그 함수를 선언하지 않는 것입니다. 함수 선언이 없으면 호출할 함수도 없습니다. 이보다 더 쉬울 수도 없을 것입니다. 그러나 C++이 독자 대신 함수들을 선언하는 경우가 있으며, 그런 경우 클라이언트가 그 함수들을 호출하지 못하게 하기는 그리 쉽지 않습니다.
그런 상황은 소위 "특수 멤버 함수들", 즉 C++이 필요에 따라 자동으로 작성하는 멤버 함수들에서만 발생합니다. 그런 함수들은 Chapter(17)에서 좀 더 자세히 논의합니다. 일단 지금은 복사 생성자와 복사 배정 연산자만 살펴보기로 합니다. 이번 장의 내용 대부분은 C++98의 흔한 관행(practice)들에 대한, C++11이 제공하는 더 나은 관행들에 관한 것입니다. C++98에서는, 사용을 금지하려는 멤버 함수가 거의 항상 복사 생성자 또는 복사 배정 연산자(또는 둘 다)였습니다.
그런 함수들의 사용을 방지하기 위한 C++98의 접근방식은 그런 함수들을 private(비공개)으로 선언하고 정의는 하지 않는 것입니다. 예를 들어 C++ 표준 라이브러리의 iostream 계통 구조의 뿌리 부근에는 basic_ios라는 클래스 템플릿이 있습니다. 모든 입출력 스트림 클래스는 이 클래스를 상속합니다(직접적으로든 간접적으로든). 입력 스트림 객체나 출력 스트림 객체는 복사하지 않는 것이 좋은데, 이는 그런 객체에 대한 복사 연산이 구체적으로 어떤 일을 해야 할 것인지가 명확하지 않기 때문입니다. 예를 들어 입력 값들의 스트림을 나타내는 istream 객체에서 그 입력 값 중 일부는 이미 읽었고 나머지는 이후에 읽힐 가능성이 있다고 합시다. istream 객체를 복사한다면, 이후에 읽힐 값들뿐만 아니라 이미 읽은 값들도 복사해야 할까요?? 이런 질문을 다루는 가장 좋은 방법은, 이런 질문이 제기될 여지를 아예 없애는 것입니다. 스트림 객체의 복사를 방지하면 그런 여지가 사라집니다.
입출력 스트림 클래스들의 복사를 방지하기 위해, C++98 표준은 basic_ios를 다음과 같이 정의합니다(주석들도 표준에서 가져온 것입니다).
<의사 코드>
template
class basic_ios : public ios_base {
public:
...
private:
basic_ios(const basic_ios&); // not defined
basic_ios& operator=(const basic_ios&); // not defined
};
이 함수들은 private 섹션에 선언되어 있으므로 클라이언트가 호출할 수 없습니다. 또한, 함수들을 의도적으로 정의하지 않았기 때문에(not defined), 이들에 접근 할 수 있는 코드(이를테면 멤버 함수나 클래스의 friend 함수)에서 호출한다고 해도 정의가 없어서 링크가 실패합니다.
C++11에서는 같은 목적을 달성하는 더 나은 방법이 있습니다. 바로, 복사 생성자와 복사 배정 연산자 선언의 끝에 "= delete"를 붙이는 것입니다. = delete를 붙인 함수를 삭제된 함수(deleted function)라고 부릅니다. 다음은 C++11 표준에 명시된 basic_ios의 해당 부분입니다.
template
class basic_ios : public ios_base {
public:
...
basic_ios(const basic_ios&) = delete;
basic_ios& operator=(const basic_ios&) = delete;
...
};
이 함수들을 삭제하는 것과 private으로 선언하는 것의 차이가 단지 취향 문제인 것 같지만, 사실 그 차이는 생각보다 큽니다. 삭제된 함수는 어떤 방법으로든 사용할 수 없습니다. 따라서 멤버 함수나 friend 함수에서 basic_ios 객체를 복사하려 하면 컴파일이 실패합니다. 이는 부적절한 용례가 링크 시점에 가서야 발견되는 C++98 방식에 비해 개선된 것입니다.
삭제된 함수는 private이 아니라 public으로 선언하는 것이 관례입니다. 여기에는 이유가 있습니다. 클라이언트 코드가 멤버 함수를 사용하려 할 때, C++은 먼저 그 함수의 접근성을 점검한 후에야 삭제 여부를 점검합니다. 그런데 private 함수를 사용하는 클라이언트 코드에 대해 그 함수가 private이라는 점을 문제 삼는 컴파일러들이 있습니다. 사실 함수를 사용할 수 없는 주된 이유가 함수의 접근성 때문이 아니라는 점에서, 이는 오해의 여지를 제공합니다. 구식 코드에서 정의되지 않은 private 멤버 함수를 삭제된 함수로 변경할 때에는, 새 함수들을 public으로 선언하면 대체로 더 나은 오류 메시지가 나온다는 점을 명심하기 바랍니다.
삭제된 함수의 중요한 장점 하나는, 그 어떤 함수도 삭제할 수 있다는 것입니다. 반면 private은 멤버 함수에만 적용할 수 있습니다. 예를 들어 정수 값을 하나 받아서 그것이 행운의 번호인지의 여부를 돌려주는 비멤버 함수(non-member function)가 있다고 합시다.
'컴퓨터과학' 카테고리의 다른 글
[Effective C++] (12-1) 재정의 함수들을 override로 선언할 것 (0) | 2020.05.29 |
---|---|
[Effective C++] (11-2) 정의되지 않은 비공개 함수보다 삭제된 함수를 선호할 것 (0) | 2020.05.28 |
[Effective C++] (10-3) 범위 없는 enum보다 범위 있는 enum을 선호할 것 (0) | 2020.05.28 |
[Effective C++] (10-2) 범위 없는 enum보다 범위 있는 enum을 선호할 것 (0) | 2020.05.28 |
[Effective C++] (10-1) 범위 없는 enum보다 범위 있는 enum을 선호할 것 (0) | 2020.05.28 |