2012-10-09 2 views
1

이 디자인 문제가 발생했습니다. 일부 쿼리에 응답 할 때존재하지 않는 데이터에 대한 참조를 반환하는 것에 대한 설계

virtual double & f(double x); 

기본 클래스는 서브 클래스에서 일부 데이터에 대한 참조를 요청 중 하나를 호출합니다 :이 같은 몇 가지 방법을 정의하는 기본 클래스가

: 여기에 이야기 . 문제는 이러한 f 메서드가 하위 클래스에 선택적 데이터 조각을 반환한다는 것입니다. 즉, 하위 클래스에 따라 어떤 f가 반환할지 묻는 것은 전혀 존재하지 않을 수 있습니다. 기본 클래스의 논리는 존재하지 않을 때 하위 클래스에서 f()를 호출하지 않을 것이라고 보장 할 수 있으므로 원칙적으로이 하위 클래스는이 메서드를 구현할 필요가 없지만 서브 클래스를 관계없이 인스턴스화할 수 있어야하기 때문에 순수 가상 메소드. 불편하게도 f의 반환 유형은 참조 일 필요가 있으므로 단순히 정크 값을 반환 할 수는 없습니다.

기본적으로 프로그램이 제대로 작동하면 어쨌든 호출되지 않을 것이지만 실수로 호출을 잡아서 디버깅하는 데 도움이되는 더미 참조를 반환하는 "우아한"방법을 찾고 있습니다. f의 서명은 가독성에 중요하므로 이상적으로는 변경하지 않으려합니다.

클래스에 더미 정적 정적 DATA_NOT_EXIST 변수를 선언하고 반환 할 수 있다고 추측합니다.하지만보기에는 조금 엉망입니다. 더 좋은 아이디어? 당신이해야 할 때 "null"참조를 반환하는 가장 좋아하는 방법은 무엇입니까?

미리 감사드립니다.


편집 :

가 대신 서브 클래스에서 선언 떨어져 더 나은 것 할 수 있지만 기본 클래스는 모든 서브 클래스에 적용 할 수없는의 일부 로직을 처리하기 위해 노력하고 있기 때문에 나는, 기본 클래스에서 이러한 방법을 유지하려면 (이 자체가 나쁜 디자인입니까?). 이런 식으로 기본 클래스는 마우스를 나타내며 하위 클래스의 선택적 데이터는 왼쪽/가운데/오른쪽 버튼을 나타냅니다. 특정 하위 클래스에는 특정 버튼이 없을 수도 있지만 어쨌든 기본 클래스는 버튼이있을 수도 있고 없을 수도 있다는 가정하에 버튼 클릭을 처리합니다.


편집 : 는 여기에 내가 할 무엇을 의미하는지 설명하기 위해 일부 소스 코드입니다. 기본 클래스에서 :

#include <cassert> 
struct Base 
{ 
    double & basef(int x) 
    { 
    assert(x >= 0 && x < 20); 
    if (x < 10) return f1(x); 
    else return f2(x); 
    } 
    virtual double & f1(int x); 
    virtual double & f2(int x); 
}; 

struct Sub1 : Base 
{ 
    double & f1(int x) { return a[x]; } 
    double & f2(int x) { return b[x - 10]; } 
    double a[10]; 
    double b[10]; 
}; 

struct Sub2 : Base 
{ 
    double & f1(int x) { return a[x]; } 
    double & f2(int x) { /* nothing to return here! */ } 
    double a[10]; 
}; 
+2

왜이 기능들을 서브 클래스에 넣지 않는가? 그것들이 모든 서브 클래스에 구현 될 수 없다면, 그들은 (일반적으로) 수퍼 클래스에 존재할 곳이 없다. – Mankarse

+0

전반적으로 디자인에 결함이있는 것 같지만 변경할 수 없다면 하위 클래스에서'f '가 호출되어서는 안되는 것을'어설 션 (false)'하는 것이 가장 좋습니다. – Mankarse

+0

기본 클래스 디스패처에서 호출해야합니다. 그 디스패처는 기본 클래스에 있어야합니다. 기본 클래스를 마우스와 같은 것을 나타내는 것이라고 생각하면됩니다. 왼쪽/가운데/오른쪽 버튼. 모든 마우스에 모든 키가있는 것은 아니지만 기본 클래스에서 이러한 공통 스키마를 가정하고 기본 클래스에서 키 클릭을 처리하게하는 것이 더 좋습니다. – fang

답변

2

몇 가지 옵션이 있습니다. 널 포인터를 역 참조하여 프로그램에 정의되지 않은 동작을 도입 할 필요가 없으므로 지금 두 가지 대답으로 제안되었습니다.

1) 예외를 throw하십시오.boost::optional<double&>

// default implementation in base class 
virtual double& f2(int x) 
{ 
    throw std::runtime_error("No value defined"); 
} 

2) 사용 : 예측 불가능 성을 데려가과 설계의 일부로 만들

// default implementation in base class 
virtual boost::optional<double&> f2(int x) 
{ 
    return boost::optional<double&>(); 
} 

// call-site 
auto value = x->f2(0); 
if (value) 
    do_something(*value); 

3)) double* (가난한 사람의 boost::optional를 사용

// default implementation in base class 
virtual double* f2(int x) 
{ 
    return nullptr; 
} 

// same call-site as before 

4) 근본적으로 상황을 피하기 위해 디자인을 변경하십시오.

이 모두는 신뢰할 수 있고 예측 가능하기 때문에 더 좋은 해결책입니다.

+0

(2)에서 장면 뒤에서 어떤 부스트를하는지 빨리 설명해 주시겠습니까? – fang

+0

@fang : 기본적으로 숫자 3,'boost :: optional 0x'의'T'가 참조 유형입니다. – GManNickG

1
double& Dummy() { return *(double*)nullptr; } 

컴파일러 이것이 nullptr 액세스임을 깨닫게 똑똑하고, 어떤 < 4096처럼 충돌이 발생할 가능성이 다른 값을 사용해보십시오 (그것에 대해 경고하는 경우 합리적으로 현대적인 시스템은 첫 번째 메모리 페이지를 완전히 offlimits하여 nullpointer 역 참조를 잡아야합니다.) ~ 0 또는 비슷한 것으로 포인터를 전역 변수로 만듭니다. 물론

, 나는 보통

double& Dummy() { ASSERT(false); } 

처럼 뭔가를 할 것이며, 컴파일러는 반환하지 않는 기능에 대해 알고하지 않는 경우에만 참조를 반환합니다.

+0

이것은이 질문에서 downvoted와 deleted answer와 같습니다. 문제를 해결하기 위해 프로그램에 정의되지 않은 동작을 도입하지 마십시오. – GManNickG

+0

@GManNickG : 실제로 UB입니까? 만약 당신이 실제로하는 일이'sizeof'를 적용한다면, 참조가 실제로 특정 실행 중에 접근된다면 (UB가 될 수도 있습니다.) ('((struct foo *) NULL) -> somefield'를 쓰는 것과 비슷합니다. 그것을), 그러나 나는 확실하지 않다. 그렇다면 ref가 NULL 일 때 아무런 실행도 실제로 액세스하지 않으면 UB가 발생하지 않습니다. –

+1

@j_random_hacker : 표준 적으로, 그것은 UB가되도록 의도되었지만, 마지막으로 보았을 때 이것이 UB인지 여부에 대한 활발한 논란이있었습니다.'int * i = nullptr; * i;', 값으로 아무 것도하지 않기 때문에. 'sizeof'에 대한 인수가 평가되지 않도록 명시 적으로 지정 되었기 때문에'sizeof' 예제는 괜찮습니다. 그럼에도 불구하고 코드가 실행되지 않으면 UB가 없지만, 나쁜 상황에서 코드를 실행하려는 경우, 예측할 수 없거나 잘못 정의 된 작업을 수행하면 왜 악화됩니까? 그냥 예외를 던져. – GManNickG

관련 문제