Это может быть очень простой вопрос, но я пытался найти ответ в SO и не смог найти на него точного ответа.
Какой смысл инициализировать указатель const
с помощью nullptr
?
int *const pi = nullptr;
Почему компилятор не выдает предупреждение или ошибку, видя, что указатель pi
нельзя эффективно использовать в любом месте программы?
Я попытался скомпилировать это с помощью g++ -w
.
Кроме того, можете ли вы привести какой-нибудь вариант использования, когда указатель const
будет инициализирован nullptr
для действительной цели в реальном коде?
🤔 А знаете ли вы, что...
C++ поддерживает объектно-ориентированное, процедурное и обобщенное программирование.
Не путайте const
, что означает: «Значения не могут измениться после инициализации» с «это значение всегда, при любых обстоятельствах nullptr
». Я имею в виду следующее:
struct foo {
int *const pi = nullptr;
foo( int* p) : pi(p) {}
};
Различные экземпляры foo
могут иметь разные значения для pi
.
Другой способ получить условие инициализации:
int *const pi = (some_condition) ? nullptr : some_pointer;
Даже с равниной
int *const pi = nullptr;
нет ничего плохого. По сути, это дает другое имя nullptr
, поэтому вы можете использовать pi
везде, где вы можете использовать nullptr
. Например, как часовой в контейнере:
int * const empty = nullptr;
for ( int* element : container) {
if (element != empty) do_something(element);
}
Это можно считать запутыванием, и прямое использование nullptr
было бы более читабельным. Хотя учтите, что значение дозорного изменяется позже, использование empty
позволяет легко заменить все его вхождения.
Хотя я согласен с тем, что инициализация указателя const
на nullptr
обычно не особенно полезна, одна ситуация, когда это может быть уместно, — это когда вы условно определяете указатель const pi
либо на nullptr
, либо на какой-либо другой (действительный) адрес, в зависимости от времени компиляции. настройки, как в следующем примере. (Квалификатор const
предотвращает непреднамеренное изменение значения другим кодом и, таким образом, нарушение вышеупомянутого условия времени компиляции.)
#include<iostream>
#define USENULL 1 // Comment out this line to get a 'valid' pointer!
int main()
{
int a = 42;
#ifdef USENULL
int* const pi = nullptr;
#else
int* const pi = &a;
#endif
int* pa = pi;
if (pa) std::cout << *pa;
else std::cout << "null pointer";
std::cout << std::endl;
return 0;
}
Такая ситуация может возникнуть, например, когда вам нужно различать отладочные и выпускные сборки.
Я попытаюсь обобщить пример @AdrianMole (второй):
Иногда фрагмент кода является вырожденным случаем или исключительным случаем более общей инструкции или шаблона. Таким образом:
В примере @AdrianMole есть внешний параметр, который определяет, должно ли pi
иметь значимое значение или нет. Вы не хотите слишком сильно искажать свой исходный код, чтобы приспособиться к этому сценарию, поэтому вы сохраняете тот же код, но используете nullptr
, вероятно, с более поздней (во время выполнения) проверкой того, есть ли у вас nullptr
или нет.
У вас может быть класс шаблона, где общее определение выглядит примерно так:
template <typename T>
class A {
int *const pi = whatever<T>();
}
со специализацией
template <>
class A<foo_t> {
int *const pi = nullptr;
}
потому что вы хотите избежать звонка whatever<foo>()
.
Вполне вероятно, что такой код можно улучшить, чтобы избежать явного присваивания nullptr
. Но было бы несколько чрезмерным подталкивать разработчиков к этому с помощью предупреждения.
(Об ошибке не может быть и речи, поскольку это совершенно корректный код с точки зрения языкового стандарта.)