Почему при инициализации константного указателя значением null нет предупреждения или ошибки?

Это может быть очень простой вопрос, но я пытался найти ответ в SO и не смог найти на него точного ответа.

Какой смысл инициализировать указатель const с помощью nullptr?

int *const pi = nullptr;

Почему компилятор не выдает предупреждение или ошибку, видя, что указатель pi нельзя эффективно использовать в любом месте программы?

Я попытался скомпилировать это с помощью g++ -w.

Кроме того, можете ли вы привести какой-нибудь вариант использования, когда указатель const будет инициализирован nullptr для действительной цели в реальном коде?

🤔 А знаете ли вы, что...
C++ поддерживает объектно-ориентированное, процедурное и обобщенное программирование.


2
717
3

Ответы:

Не путайте 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. Но было бы несколько чрезмерным подталкивать разработчиков к этому с помощью предупреждения.

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