2013-04-11 2 views
3

으로 당신이 참조 (D-참조)를 사용할 수 있습니다 here 언급 PIMPL 관용구의 경우 참조 사용의 단점 대신 PIMPL idiom의 경우 포인터 (D-포인터)의.장점과

이 구현에 심각한 문제가 있는지, 장단점이 무엇인지 이해하려고합니다.

장점 : 때문에의 사용

  • 짧은 구문 "." "->"대신.
  • ...

단점 :

  • 새로운 ObjectPivate()이 실패하고 새로운이 포기하지 않는 어떤 경우 (예 : 새 (표준 :: nothrow) 또는 맞춤 ) nullptr을 대신 반환 하시겠습니까? 참조가 유효한지 확인하려면 추가 물건을 구현해야합니다. 포인터의 경우 그냥 사용 :
 

if (m_Private) 
    m_Private->Foo(); 
 
  • 여러 생성자의 드문 경우 솔루션이 적용되지 수 복잡한 초기화 논리와 객체을 위해. [© JamesKanze]
  • 메모리 관리를 위해 포인터를 사용하는 것이 더 자연 스럽습니다. [© JamesKanze]
  • 일부 추가 구현 세부 사항은 예외 안전 (할당 연산자의 예 구현을) 보장 (스왑 (사용)) 고려 될 필요가있다 [© 매트 양]
  • ...
  • 여기

그림에 대한 샘플 코드 : 스타일의

// Header file 

class ObjectPrivate; 

class Object 
{ 
public: 
Object(); 
virtual ~Object(); 

virtual void Foo(); 

private: 
    ObjectPrivate& m_Private; 
}; 

// Cpp file 

class ObjectPrivate 
{ 
public: 
    void Boo() { std::cout << "boo" << std::endl; } 
}; 

Object::Object() : 
m_Private(* new ObjectPrivate()) 
{ 
} 

Object::~Object() 
{ 
    delete &m_Private; 
} 

void Object::Foo() 
{ 
    m_Private.Boo(); 
} 
+2

'new ObjectPivate()'가 실패하면'throw' 할 방법을 찾아야한다고 생각합니다. 구현이 덜컹 거리는 점은 없습니다. – juanchopanza

+0

@juanchopanza 나는 그가 "실패한"의미하는 것이 확실하지 않습니다. 'new ObjectPrivate'는'operator new' (실패하면'bad_alloc'을 던집니다)와 생성자 (예외를 던지면서 만 실패를보고 할 수있는)를 호출합니다. –

+1

@JamesKanze 오른쪽.나는 OP의 ** Cons ** 섹션을 다루었지만, 그 시나리오는'new (std :: nothrow) '에서만 가능할 것으로 생각합니다. – juanchopanza

답변

5

그건 정말 문제. 나는 클래스에서 참조를 사용하지 않는 경향이 있으므로 컴파일 방화벽에서 포인터를 사용하는 것이 더 자연스러워 보입니다. 그러나 은 보통 실제 이점이 없습니다. new은 예외로 인해 실패합니다.

포인터를 선호하는 경우는 개체에 많은 생성자가있는 경우입니다. 그 중 일부는 new을 호출하기 전에 예비 계산이 일 필요합니다. 이 경우 포인터를 NULL으로 초기화 한 다음 에 공통 초기화 루틴을 호출 할 수 있습니다. 그런 경우는 드물다고 생각합니다. 그러나 . (나는 그것을 한 번 만났을 때 기억할 수있다.)

편집 :

그냥 다른 스타일의 고려 사항 : 많은 사람들이 당신이 참조하는 대신 포인터를 사용하는 경우 필요 delete &something; 같은 무언가를 좋아하지 않는다. 다시 말하지만, 메모리를 관리하는 객체는 포인터를 사용한다는 것이 더 자연 스럽습니다 (적어도 나에게는).

1

내가 생각하는 예외 안전 코드를 작성하는 것은 편리하지 않습니다.

Object::operator=(Object const&)의 첫 번째 버전이 될 수 있습니다 ObjectPrivate::operator=(ObjectPrivate const&)이 예외가 발생하는 경우 그것은 위험

Object& operator=(Object const& other) 
{ 
    ObjectPrivate *p = &m_Private; 
    m_Private = other.m_Private;  // Dangerous sometimes 
    delete *p; 
} 

. 그렇다면 임시 변수를 사용하는 것은 어떻습니까? 아하, 안돼. m_Private를 변경하려면 operator=()을 호출해야합니다.

그래서 void ObjectPrivate::swap(ObjectPrivate&) noexcept은 우리 구세주의 역할을 할 수 있습니다.

Object& operator=(Object const& other) 
{ 
    ObjectPrivate *tmp = new ObjectPrivate(other.m_Private); 
    m_Private.swap(*tmp);    // Well, no exception. 
    delete tmp; 
} 

그런 다음 void ObjectPrivate::swap(ObjectPrivate&) noexcept의 구현을 고려하십시오. ObjectPrivate에 swap() noexcept 또는 operator=() noexcept이없는 클래스 인스턴스가 있다고 가정 해 봅시다. 나는 그것이 어렵다고 생각한다.

그렇다면이 가정은 너무 엄격하고 때로는 정확하지 않습니다. 그렇다고하더라도 대부분 ObjectPrivate가 swap() noexcept을 제공 할 필요는 없습니다. 대개 데이터를 중앙화하기위한 도우미 구조이기 때문입니다.

대조적으로, 포인터는 많은 뇌 세포를 저장할 수 있습니다.

Object& operator=(Object const& other) 
{ 
    ObjectPrivate *tmp = new ObjectPrivate(*other.p_Private); 
    delete p_Private; 
    p_Private = tmp;  // noexcept ensured 
} 

스마트 포인터를 사용하면 훨씬 더 우아합니다.

Object& operator=(Object const& other) 
{ 
    p_Private.reset(new ObjectPrivate(*other.p_Private)); 
} 
1

빠른 및 명백한 추가는 :

프로

  • 참조는 0이 아니어야합니다.
  • 참조에 다른 인스턴스가 할당되지 않았을 수 있습니다.
  • 클래스 책임/구현은 더 적은 변수로 인해 더 간단합니다.
  • 컴파일러가 최적화를 할 수 있습니다.

참조가 다른 인스턴스를 할당 할 수 없습니다

  • 콘.
  • 일부 경우 참조가 너무 제한적입니다.