Это может быть очень простой вопрос, но я пытался найти ответ в SO и не смог найти на него точного ответа.
Какой смысл инициализировать указатель const
с помощью nullptr
?
int *const pi = nullptr;
Почему компилятор не выдает предупреждение или ошибку, видя, что указатель pi
нельзя эффективно использовать в любом месте программы?
Я попытался скомпилировать это с помощью g++ -w
.
Кроме того, можете ли вы привести какой-нибудь вариант использования, когда указатель const
будет инициализирован nullptr
для действительной цели в реальном коде?
nullptr
является допустимым значением для указателя, и нет причин, по которым его нельзя допускать. if (something == pi)
все еще действует.
«Пи не может быть эффективно использовано где-либо в программе». Почему вы так думаете? I можно эффективно использовать везде, где можно использовать nulltpr
. Это не неправильно, просто не очень полезно давать другое имя nullptr
Почему кто-то хочет запретить это? Точно так же вы могли бы сказать, почему что-то вроде (true and false and true and false)
должно быть запрещено, потому что оно все равно оценивается как false
. Стандарты существуют не для проверки вашей семантики.
Умный компилятор @Aziuth должен предупредить об этом, не так ли? что-то вроде предупреждения: выражение всегда оценивается как ложное.
@Cedric: «Недействительно» означает, что это не ошибка, это не значит, что не должно быть предупреждения.
ничего в нормативных документах не требует диагностики в этой ситуации, так что "должен" нет. Во всяком случае, вы объявили законный неизменяемый нулевой объект (шаблон)
@einpoklum Действительно, предупреждение было бы возможно. Я не знаю настоящей причины, по которой не выдается предупреждение, но, поскольку это «предположительно разумный» способ использования языка, такое предупреждение на практике было бы очень раздражающим, имхо. В отличие от, например. if (unsigned_type_value < 0)
, и в этом случае можно было бы явно ожидать ошибки пользователя.
@Swift-FridayPie: обратите внимание, что вопрос был отредактирован, чтобы сосредоточиться не на стандарте языка, а на поведении компилятора. Должное раскрытие: я сделал это редактирование, полагая, что оно лучше отражает то, что хочет сказать ОП.
Не путайте 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;
}
Такая ситуация может возникнуть, например, когда вам нужно различать отладочные и выпускные сборки.
Я думаю, что первый пример неуместен. Маскировка nullptr
для экономии нескольких нажатий клавиш - плохая практика кодирования ИМХО. Рассмотрите возможность сохранения только второго, который более действителен.
@einpoklum Согласен - мы не хотим поощрять плохие методы кодирования на SO! Отредактировано, чтобы удалить это.
Я попытаюсь обобщить пример @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
. Но было бы несколько чрезмерным подталкивать разработчиков к этому с помощью предупреждения.
(Об ошибке не может быть и речи, поскольку это совершенно корректный код с точки зрения языкового стандарта.)
Потому что nullptr не является недопустимым. Я предполагаю, что для числового значения, такого как «пи», это не имеет особого смысла, но это ваш выбор как разработчика. Это все еще действующий код. В качестве примера я мог бы представить, что указатель (const) намеренно имеет значение null (навсегда), потому что некоторые функции отключены в зависимости от создания экземпляра класса.