2010-12-11 3 views
6

내 코드에서 nullptr을 확인해야 할 필요가 있음을 알았습니다 (지정된 요구 사항에 따라 nullptr이 가능하지 않아도 됨).null 포인터를 피하고 다형성을 유지

그러나 다른 사람이 nullptr을 보낼 수도 있기 때문에 nullptr이 계속 발생할 수 있습니다. (불행히도 모든 사람이 읽기/쓰기를 할 수있는 것은 아닙니다.) 테스트 중에 런타임에 문제가 발생하지 않으면이 결함을 발견 할 수 없습니다. 높은 테스트 커버리지는 비싸다). 따라서 고객이보고 한 많은 출시 후 버그로 이어질 수 있습니다.

class data 
{ 
    virtual void foo() = 0; 
}; 

class data_a : public data 
{ 
public: 
    virtual void foo(){} 
}; 

class data_b : public data 
{ 
public: 
    virtual void foo(){} 
}; 

void foo(const std::shared_ptr<data>& data) 
{ 
    if(data == nullptr) // good idea to check before use, performance and forgetting check might be a problem? 
     return; 
    data->foo(); 
} 

보통 나는 값 유형을 사용하고 참조 및 복사를 전달합니다. 그러나, 어떤 경우에는 포인터 또는 참조가 필요한 다형성이 필요합니다.

그래서 다음과 같은 "컴파일 시간 다형성"을 사용하기 시작했습니다.

class data_a 
{ 
public: 
    void foo(){} 
private: 
    struct implementation; 
    std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy 
}; 

class data_b 
{ 
public: 
    void foo(){} 
private: 
    struct implementation; 
    std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy 
}; 

class data 
{ 
public: 
    data(const data_a& x) : data_(x){} // implicit conversion 
    data(const data_b& x) : data_(x){} // implicit conversion 
    void foo() 
    { 
      boost::apply(foo_visitor(), data_); 
    } 
private: 
    struct foo_visitor : public boost::static_visitor<void> 
    { 
      template<typename T> 
      void operator()(T& x){ x.foo(); }  
    }; 

    boost::variant<data_a, data_b> data_; 
} 

void foo(const data& data) 
{ 
    data.foo(); 
} 

실용적인 경우 다른 사람이이 방법을 사용하는 것이 좋습니다. 또는 나는 무엇인가 놓치고 있냐? 이 관행에 잠재적 인 문제점이 있습니까?

편집 : 사용하여 참조가

은 "문제는"당신이 (예를 들어, 객체를 반환) 참조의 소유권을 이동할 수 없습니다입니다.

data& create_data() { data_a temp; return temp; } // ouch... cannot return temp; 

를 rvalue 참조의 문제 (다형성를 rvalue 참조와 함께 작동합니까?)

는 소유권을 공유 할 수 있다는된다. shared_ptr을 기반으로

data&& create_data() { return std::move(my_data_); } // bye bye data   

A "안전"포인터가 좋은 아이디어 같은 소리 않습니다,하지만, 난 여전히 비 nullness은 어쩌면, 컴파일시에 가능하지 적용됩니다 솔루션 싶습니다.

+0

코드를 다시 편집해야합니다. 어떤 곳에서는 포인터와 참조를 섞고 있고, 포인터는 null 일 수 있고, 역 참조는'->'연산자를 사용합니다; 참조는 null이 될 수 없으며, 참조 해제는'.' 연산자를 사용합니다. –

+0

흠 ... 문제가 보이지 않습니다. 그것이 어디 있는지 지적 해 주시겠습니까? – ronag

+0

기타 작은 것들 : * 모든 * 생성자를 정의한 후에는 컴파일러가 기본 생성자를 생성하지 않고 (private으로 정의 할 필요가 없음) 기본 생성자에 필요하지 않은 하나의 생성자를 실제로 비활성화하려는 경우 - - 비공개로 선언하는 것이 좋지만 정의하지는 않습니다. 비공개 인 것은 컴파일시에 다른 클래스의 사용을 캐치 할 것이고, 링크를 정의하지 않으면 링크시 자신의 클래스 (또는 친구들)에서 사용을 캐치 할 것입니다. –

답변

3

개인적으로 유형에 null 가능성을 인코딩하는 것을 선호하므로 boost::optional을 사용하십시오.

  • 는 완벽하게 분명하다 항상이
  • data_holder (null 이외) 또는 boost::optional<data_holdder>

이 방법의 측면에서 사용자의 인터페이스를 정의 data (그러나 수 다형성)를 소유하는 data_holder 클래스를 만듭니다 그것이 null 일지 어떨지

이제 어려운 부분은 null 포인터에 보유하지 말아야 할 data_holder입니다. data_holder(data*) 형식의 생성자로 정의하면 생성자가 throw 할 수 있습니다.

한편, 몇 가지 인수를 취하고 실제 생성을 Factory (Virtual Constructor Idiom을 사용하여)로 연기 할 수 있습니다. 당신은 여전히 ​​공장의 결과를 확인하고 (필요하다면 던져 짂다), 단 하나의 건설 지점 이라기보다는 하나의 공장 (공장)을 가지고있다.

boost::make_shared도 확인하여 실제 전달되는 인수 전달을 확인할 수 있습니다. 당신이 C++ 0X가있는 경우에, 당신은 인수가 효율적으로 전달하고 얻을 수 있습니다 :

template <typename Derived> 
data_holder(): impl(new Derived()) {} 

// Other constructors for forwarding 

비공개로 기본 생성자 (비 템플릿) 선언 (그리고 그것을 정의) 사고를 방지하기 위해 잊지 마세요 요구.

4

항상 null object pattern 및 참조 용으로 만 사용할 수 있습니다. 당신은 (당신이 할 수있는 일종의 할 수 있지만 그것은 사용자의 오류입니다) null 참조를 전달할 수 없습니다.

+0

아직 Null-object-pattern에 대한 심사 위원이 나와 있습니다. 올바르지 않게 사용 된 경우처럼 프로그램은 잘못된 상태에서도 실행을 계속할 수 있습니다. 그러나 나는 대답의 두 번째 부분 때문에 +1했다. nullptr을 원하지 않으면 참조를 취하십시오. 나는 사용자가 여전히 무지 또는 속임수로 이것을 깨뜨릴 수 있다는 것에 동의하지만, 다른 모든 경우에 이것은 가장 안전한 배팅이다. –

2

non-null pointer은 널리 알려진 개념으로, 예를 들어 C의 안전한 하위 집합에 사용됩니다. 네, 유리할 수 있습니다.

그리고 스마트 포인터를 사용해야합니다. 유스 케이스에 따라 boost::shared_ptr 또는 tr1::unique_ptr과 비슷한 것으로 시작하는 것이 좋습니다. 그러나 assert이 아닌 operator*, operator->get()에 null이 아닌 값을 지정하는 대신 예외를 throw합니다.

이메일 : 잊어 버려요. 이 일반적인 접근 방식이 도움이된다고 생각하지만 (정의되지 않은 동작 등은 아님), 컴파일 타임에 컴파일러가 아닌 null-ness에 대한 검사를 제공하지 않습니다. 이를 위해 코드에서 퍼블릭하게 널 포인터가 아닌 포인터를 사용하면 언어 지원이 없어도 여전히 누출 될 수 있습니다.

1

일반적으로 위에서 설명한대로 가상 함수를 사용하는 것은 원하는 인터페이스를 구현하는 모든 클래스에 대해 알고 싶지 않기 때문입니다. 예제 코드에서는 (개념적) 인터페이스를 구현하는 모든 클래스를 열거합니다. 시간이 지남에 따라 구현을 추가하거나 제거해야 할 때 좌절하게됩니다.

또한 다형성을 사용하면 특정 클래스에 대한 종속성을 제한하는 것이 더 쉽지만 클래스 데이터는 모든 클래스에 종속되므로 종속성 관리가 방해가됩니다. pimpl 관용구를 사용하면이 문제를 완화 할 수 있지만 항상 pimpl 관용구를 성가신 것으로 간주합니다 (한 개념을 나타 내기 위해 두 가지 클래스가 동기화되어 있어야 함).

참조 또는 확인 된 스마트 포인터를 사용하는 것이 더 간단하고 깨끗한 솔루션처럼 보입니다. 다른 사람들은 이미 그 사람들에 대해 언급하고 있으므로 지금은 자세히 설명하지 않겠습니다.

관련 문제