Изменение not_null для запрета сравнений с nullptr

Я пытаюсь получить проверку во время компиляции для назначения и проверки на ноль. Причина в том, что я использую "магический" ненулевой указатель для представления отключенного состояния чего-либо, и легко забыть, что он использует этот магический указатель, и по ошибке назначить и проверить nullptr. (Тем более, что это было то, что код использовал исторически.)

Понимая, что это была проблема, которую люди уже решили, я искал и нашел not_null как часть C++ Core Guidelines, что звучало многообещающе. Вот MIT-лицензированная реализация от Microsoft:

https://github.com/Microsoft/GSL/blob/5cbde3008aa43a9c5f6c219ee15b8388336d4433/include/gsl/pointers

Но это только останавливает присваивания nullptr, но не сравнения:

#include <iostream>
#include "include/gsl/gsl"

int main() {
    int i;
    gsl::not_null<int*> ptr (&i);
    /* gsl::not_null<int*> ptr_error (nullptr); */ // this errors

    if (ptr != nullptr)
       std::cout << "No compile error on compare--this prints\n";
}

Это прискорбно.:-/

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

  2. Какой хороший минор редактировать not_null изменить его, чтобы запретить эти сравнения, например nullptr == ptr, ptr == nullptr, так далее? Грубая сила, я бы просто = delete; некоторые перегрузки == и!= до тех пор, пока это не привело к ошибкам в нужном мне случае, но я надеялся на использование заранее написанного кода, что кто-то подумал бы об этом и сделал бы это в целом "правильно".

2 ответа

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

template<class Lhs, class Rhs>
bool hasSameValue(Lhs lhs, Rhs rhs){
  if(lhs == nullptr || rhs == nullptr)
    return lhs == rhs;

  return *lhs == *rhs;
}

gsl::not_null<int*> ptr = /* [...] */;
shared_ptr<int> sptr = /* [...] */;
hasSameValue(ptr, sptr);

Если вы все еще хотите запретить чеки с nullptr :

bool operator==(std::nullptr_t nptr) = delete;
bool operator!=(std::nullptr_t nptr) = delete;

Маркировки их как удаленных должно быть достаточно. Обратите внимание, что gsl::not_null не должен наследовать от класса, который определяет их, чтобы сделать это. Если он наследуется от класса, который его определяет, просто сгенерируйте исключение (даже если это будет только ошибка времени выполнения).

Вам просто нужно реализовать операторы равенства для std::nullptr_t тип:

// Example program
#include <iostream>
#include <string>

class Test
{
public:
  Test(int value) : value(value) {}

  bool operator == (std::nullptr_t n)
  {
      return value == 0;
  }

  bool operator != (std::nullptr_t n)
  {
      return value != 0;
  }

private:
  int value;    
};

int main()
{
    Test a(0);
    Test b(1);
    std::cout << (a == nullptr) << ", " << (b == nullptr) << "\n";
    std::cout << (a != nullptr) << ", " << (b != nullptr) << "\n";
}

или если вы хотите ошибку компиляции, просто удалите те же операторы.

Другие вопросы по тегам