2012-10-09 4 views
1

Я столкнулся с этой проблемой дизайна. Вот история:Дизайн о возврате ссылки на несуществующие данные

У меня есть базовый класс, который определяет несколько методов, как это:

virtual double & f(double x); 

Базовый класс будет позвонить по одному из них, чтобы попросить ссылки на некоторые данные в подклассе при ответе на некоторые вопросы , Проблема в том, что эти методы f возвращают необязательный фрагмент данных в подкласс. Другими словами, в зависимости от подкласса, то, что f просит вернуть, может вообще не существовать. Логика базового класса может гарантировать, что он не будет называть такой f() в подклассе, когда он не существует, поэтому в принципе этому подклассу не требуется реализовать этот метод, но я не хочу метод чистый виртуальный, потому что подкласс должен быть реалистичным независимо. Неудобно возвращаемый тип f должен быть ссылкой, поэтому я не могу просто вернуть некоторое значение нежелательной почты.

Так что в основном я ищу «элегантный» способ вернуть фиктивную ссылку, которая не будет вызываться в любом случае, если программа работает правильно, но в идеале поможет отладки, поймав ошибки. Подпись f важна для читаемости, поэтому в идеале я не хочу ее менять.

Я полагаю, что я могу объявить старую двойную переменную DATA_NOT_EXIST в классе и вернуть ее, но она выглядит немного уродливой. Любые лучшие идеи? Каков ваш любимый способ вернуть ссылку «null», когда вам нужно?

Заранее спасибо.


Edit:

Я хочу, чтобы эти методы в базовом классе, хотя они, кажется, лучше объявлено в подклассах вместо этого, так как базовый класс пытается справиться с некоторой логикой, что это не применим ко всем подклассам (это само по себе плохой дизайн?). Что-то вроде этого: базовый класс представляет собой мышь, а дополнительные данные в подклассах представляют собой кнопки «влево/вправо/вправо». Любой конкретный подкласс может не иметь определенных кнопок, но в любом случае базовый класс по-прежнему обрабатывает нажатия кнопок, предполагая, что могут быть или не быть кнопки.


Edit: Вот некоторые исходный код, чтобы проиллюстрировать, что я имею в виду сделать. В базовом классе:

#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

В целом, ваш дизайн кажется ошибочным, но если вы не можете его изменить, лучше всего будет просто «утверждать (false)» в подклассах, где 'f' никогда не следует вызывать. – Mankarse

+0

Они должны быть вызваны диспетчером базового класса. Этот диспетчер должен находиться в базовом классе. Подумайте, как базовый класс представляет что-то вроде мыши, и дополнительные данные, например. левый/средний/правый. Не все мыши имеют все ключи, но все же имеет смысл предположить такую ​​общую схему в базовом классе и позволить базовому классу обрабатывать ключевые клики. – fang

ответ

2

У вас есть несколько вариантов, ни один из них не требует ввода неопределенного поведения в вашу программу путем разыменования нулевых указателей, как было предложено двумя ответами сейчас.

1) Бросить исключение.Заберите непредсказуемость и сделать его частью вашего дизайна:

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

2) Используйте boost::optional<double&>:

// 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: В основном номер три, фактически, когда 'T' в' boost :: optional 'является ссылочным типом. – GManNickG

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

Если ваш компилятор достаточно умен, чтобы понять, что это доступ nullptr и предупреждает об этом, попробуйте использовать другое значение, которое может вызвать аварию, как и все < 4096 (разумно современные системы должны иметь первую страницу с полной памятью, чтобы перехватывать ненужные фрагменты), ~ 0 или что-то подобное, или сделать указатель глобальной переменной.

Конечно, я, как правило, сделать что-то вроде

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

и возвращает только ссылку, если компилятор не знает о функциях, которые не возвращают.

+0

Это то же самое, что и ответ с нисходящим и удаленным ответом на этот вопрос. Не вводите неопределенное поведение в вашу программу для решения проблемы. – GManNickG

+0

@GManNickG: Это на самом деле UB? Я подозреваю, что это может быть только UB, если ссылка действительно доступна во время определенного выполнения (подобно тому, как вам разрешено писать '((struct foo *) NULL) -> somefield', если все, что вы на самом деле делаете, это применить' sizeof' к нему), но я не уверен. Если это так, и если никакое выполнение никогда не обращается к нему, когда ref имеет значение NULL, то не будет возникать UB. –

+1

@j_random_hacker: стандартно, он предназначен для UB, хотя в последнее время я смотрел, что это была активная проблема: должен ли это быть UB: 'int * i = nullptr; * i; ', поскольку он ничего не делает со значением. Пример 'sizeof' - это нормально, потому что аргумент' sizeof' явно указан для того, чтобы его не оценивали. Несмотря на это, да, если код никогда не запускается, то нет UB, но дело в том, что если вы собираетесь запускать код в плохую ситуацию, зачем делать это хуже, делая что-то непредсказуемое или плохое определение? Просто выбросьте исключение. – GManNickG

Смежные вопросы