상세 컨텐츠

본문 제목

항목 6) 암시적으로 생성되는 함수들을 금지하기

C++/Effective C++

by DeaKyungLee 2021. 12. 5. 11:58

본문

컴파일러가 만들어내는 암시적 함수가 불필요하다면 확실하게 금지시키자.

( 해당 항목은 "항목 5) C++가 자동으로 만들고 호출하는 함수들" 를 보았다는 전제하에 진행된다. )

 

만약 어떤 객체에 대해서 복사를 금지 시키고 싶다면 어떻게 해야 할까?

단순히 해당 객체를 복사하는 함수를 구현하지 않으면 될까?

일반적으로는 그렇겠지만, (기본) 복사 생성자 및 복사 대입 연산자라면 이야기가 달라진다.

이들은 따로 만들지 않으면, 컴파일러가 자동으로 생성해버리기 때문이다.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
 
using namespace std;
 
class HomeFroSale {
};
 
int main()
{
	HomeFroSale h1, h2;
	HomeFroSale h3(h1);
 
	h2 = h1;
	return 0;
}

즉, 위와 같은 상황에서 h1의 복사를 막는 방법은 무엇일까?

복사 생성자 및 복사 대입 연산자를 명시적으로 선언하고 실제 복사 동작은 하지 않으면?

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
 
using namespace std;
 
class HomeFroSale {
public:
	int pdata;
	HomeFroSale() {  };  // 기본 생성자
	HomeFroSale(int data): pdata(data) {  };
	~HomeFroSale() {  }; // 소멸자
 
	HomeFroSale(const HomeFroSale& rhs) {  }; // 복사 생성자
	HomeFroSale& operator=(const HomeFroSale& rhs) { HomeFroSale temp; return temp; }; // 복사 대입 연산자
};
 
int main()
{
	HomeFroSale h1(10), h2;
	HomeFroSale h3(h1);
 
	h2 = h1;
	cout << h2.pdata << "\n";
	cout << h3.pdata << "\n";
 
	return 0;
}

확실히 h1의 값이 복사되진 않지만, 성공적으로 컴파일 및 실행이 된다는 것이 문제점이다.

여기서 원하는 것은, 복사가 안되는 것보다도 복사 행위 자체를 아예 금지 시키는 방법이다.

항목 5) 의 마지막에도 잠깐 언급했지만, private 키워드를 이용하는 방법이 존재한다.

거기다가 구현 자체를 하지 않으면 클래스 멤버 함수 및 프렌드(friend) 함수에서도 접근이 불가능해진다.

즉, 아래와 같은 방법이다.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
 
using namespace std;
 
class HomeFroSale {
public:
	int pdata;
	HomeFroSale() {  };  // 기본 생성자
	HomeFroSale(int data): pdata(data) {  };
	~HomeFroSale() {  }; // 소멸자
private:
	HomeFroSale(const HomeFroSale& rhs); // 복사 생성자
	HomeFroSale& operator=(const HomeFroSale& rhs); // 복사 대입 연산자
};
 
int main()
{
	HomeFroSale h1(10), h2;
	HomeFroSale h3(h1);
 
	h2 = h1;
	cout << h2.pdata << "\n";
	cout << h3.pdata << "\n";
 
	return 0;
}

이렇게 하면 프렌드 함수나 멤버 함수에서의 접근도 링커가 막아준다.

이것으로도 충분하다면 상관없지만,

프렌드 및 멤버 함수에서의 접근을 링커 단계가 아니라 컴파일 단계에서 막아버리는 방법도 존재한다.

( 최종적으로 이 방법이 실제로 가장 많이 사용된다. )

 

Uncopyable Class

 

class UnCopyable {
protected:
	UnCopyable() { };
	~UnCopyable() { };
 
private:
	// 선언만 함으로서 friend를 통한 호출도 막음
	UnCopyable(const UnCopyable&);
	UnCopyable& operator=(const UnCopyable&);
};
class HomeFroSale : private UnCopyable{
public:
	int pdata;
	HomeFroSale() {  };  // 기본 생성자
	HomeFroSale(int data): pdata(data) {  };
 
public:
	void SomeFunc()
	{
		HomeFroSale h1(10), h2;
		HomeFroSale h3(h1);
 
		h2 = h1;
		cout << h2.pdata << "\n";
		cout << h3.pdata << "\n";
	}
};
 
int main()
{
 	HomeFroSale test;
 	test.SomeFunc();
 
	HomeFroSale h1(10), h2;
	HomeFroSale h3(h1);
 
	h2 = h1;
	cout << h2.pdata << "\n";
	cout << h3.pdata << "\n";
 
	return 0;
}

보다시피 이전의 방법과는 다르게 링커 단계보다 빠른 컴파일 단계에서 내부 멤버 함수인 SomeFunc 에서 에러가 발생한다.

실제로 boost 라이브러리에서 noncopyable.hpp 에 구현되어서 사용되는 방식이다.

더보기
//  Boost noncopyable.hpp header file  --------------------------------------//

//  (C) Copyright Beman Dawes 1999-2003. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

//  See http://www.boost.org/libs/utility for documentation.

#ifndef BOOST_CORE_NONCOPYABLE_HPP
#define BOOST_CORE_NONCOPYABLE_HPP

#include <boost/config.hpp>

namespace boost {

//  Private copy constructor and copy assignment ensure classes derived from
//  class noncopyable cannot be copied.

//  Contributed by Dave Abrahams

namespace noncopyable_  // protection from unintended ADL
{
#ifndef BOOST_NONCOPYABLE_BASE_TOKEN_DEFINED
#define BOOST_NONCOPYABLE_BASE_TOKEN_DEFINED

// noncopyable derives from base_token to enable Type Traits to detect
// whether a type derives from noncopyable without needing the definition
// of noncopyable itself.
//
// The definition of base_token is macro-guarded so that Type Trais can
// define it locally without including this header, to avoid a dependency
// on Core.

  struct base_token {};

#endif // #ifndef BOOST_NONCOPYABLE_BASE_TOKEN_DEFINED

  class noncopyable: base_token
  {
  protected:
#if !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) && !defined(BOOST_NO_CXX11_NON_PUBLIC_DEFAULTED_FUNCTIONS)
      BOOST_CONSTEXPR noncopyable() = default;
      ~noncopyable() = default;
#else
      noncopyable() {}
      ~noncopyable() {}
#endif
#if !defined(BOOST_NO_CXX11_DELETED_FUNCTIONS)
      noncopyable( const noncopyable& ) = delete;
      noncopyable& operator=( const noncopyable& ) = delete;
#else
  private:  // emphasize the following members are private
      noncopyable( const noncopyable& );
      noncopyable& operator=( const noncopyable& );
#endif
  };
}

typedef noncopyable_::noncopyable noncopyable;

} // namespace boost

#endif  // BOOST_CORE_NONCOPYABLE_HPP

 

Things to Remember

  • 컴파일러에서 자동으로 제공하는 암시적 함수 생성 기능을 금지 시키려면, 대응 되는 멤버 함수를 private로 선언한 후에 실제 구현은 하지 않는 방법이 있다.
  • 혹은 Uncopyable 과 같은 기본 클래스를 사용해서 상속 받는 방법 역시 존재한다.

관련글 더보기

댓글 영역