Избежание нулевых указателей и сохранение полиморфизма - PullRequest
6 голосов
/ 11 декабря 2010

В своем коде я только что заметил, что мне довольно часто нужно проверять наличие nullptr, хотя nullptr не должно быть возможным (в соответствии с указанными требованиями).

Однако, 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?) Становится невозможной для совместного использования.

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

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

Ответы [ 4 ]

4 голосов
/ 11 декабря 2010

Вы всегда можете использовать шаблон нулевого объекта и только ссылки.Вы не можете передать (ну, в некотором роде, но это ошибка пользователя) нулевую ссылку.

3 голосов
/ 11 декабря 2010

Лично я предпочитаю кодировать нулевую возможность в типе и, таким образом, использовать boost::optional.

  • Создать класс data_holder, который всегда владеет data (но допускает полиморфизм)
  • Определите ваши интерфейсы в терминах data_holder (не ноль) или boost::optional<data_holdder>

Таким образом, совершенно ясно, может ли он быть нулевым.

Теперь самое сложное - заставить data_holder никогда не удерживать нулевой указатель.Если вы определите его с помощью конструктора вида data_holder(data*), то конструктор может сгенерировать.

С другой стороны, он может просто принять некоторые аргументы и отложить фактическую конструкцию до Фабрики (используя ВиртуальныйКонструктор Идиома).Вы все еще проверяете результат фабрики (и бросаете, если необходимо), но у вас есть только одно место для проверки (фабрика), а не каждая отдельная точка построения.

Вы можете также проверить boost::make_shared, чтобы увидеть аргумент переадресации в действии.Если у вас есть C ++ 0x, вы можете эффективно переадресовывать аргументы и получать:

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

// Other constructors for forwarding

Не забудьте объявить конструктор по умолчанию (не шаблон) как частный (и не определять его), чтобы избежатьслучайный звонок.

2 голосов
/ 11 декабря 2010

A non-null pointer - широко известная концепция, используемая, например, в безопасных подмножествах C.Да, это может быть выгодно.

И для этого вы должны использовать умный указатель.В зависимости от вашего варианта использования, вы можете начать с чего-то похожего на boost::shared_ptr или tr1::unique_ptr.Но вместо того, чтобы assert указывать ненулевое значение на operator*, operator-> и get(), выведите исключение.

ETA: забудьте об этом.Хотя я думаю, что этот общий подход полезен (без неопределенного поведения и т. Д. И т. Д.), Он не предоставляет вам проверок на время компиляции на ненулевое значение, что вы, вероятно, захотите.Для этого вы должны использовать такие ненулевые указатели повсеместно в вашем коде, и без языковой поддержки это все равно будет утечкой.

1 голос
/ 11 декабря 2010

Обычно, когда вы используете виртуальные функции, как вы описали выше, это потому, что вы не хотите знать обо всех классах, которые реализуют желаемый интерфейс.В вашем примере кода вы перечисляете все классы, которые реализуют (концептуальный) интерфейс.Это расстраивает, когда вам нужно со временем добавлять или удалять реализации.

Ваш подход также мешает управлению зависимостями, поскольку данные вашего класса зависят от всех классов, тогда как использование полиморфизма упрощает ограничение зависимости от определенных классов.,Использование идиомы pimpl смягчает это, но я всегда рассматривал идиому pimpl как вид раздражения (поскольку у вас есть два класса, которые должны оставаться в синхронизации, чтобы представлять одну концепцию).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...