Почему не законно преобразовывать «указатель на указатель на неконстантный» в «указатель на указатель на константный» - PullRequest
35 голосов
/ 08 февраля 2010

Это допустимо для преобразования указателя на неконстантный в указатель на констант.

Тогда почему не законно преобразовывать указатель в указатель на неконстантный в указатель на указатель на константный ?

Например, почему следующий код недопустим:

char *s1 = 0;
const char *s2 = s1; // OK...
char *a[MAX]; // aka char **
const char **ps = a; // error!

Ответы [ 5 ]

35 голосов
/ 08 февраля 2010

Из стандарта:

const char c = 'c';
char* pc;
const char** pcc = &pc;   // not allowed
*pcc = &c;
*pc = 'C';                // would allow to modify a const object
18 голосов
/ 08 февраля 2010

Игнорируя ваш код и отвечая на принцип вашего вопроса, смотрите эту запись в comp.lang.c FAQ: Почему я не могу передать char ** в функцию, которая ожидает const char **?

Причина, по которой вы не можете присвоить значение char ** указателю const char **, несколько неясна. Учитывая, что классификатор const существует вообще, компилятор хотел бы помочь вам выполнить ваши обещания не изменять значения const. Вот почему вы можете назначить char * для const char *, но никак не наоборот: «безопасно» добавить «1012 * -ность» к простому указателю, но было бы опасно убирать его. Однако предположим, что вы выполнили следующую более сложную серию заданий:

const char c = 'x';    /* 1 */
char *p1;              /* 2 */
const char **p2 = &p1; /* 3 */
*p2 = &c;              /* 4 */
*p1 = 'X';             /* 5 */

В строке 3 мы присваиваем char ** const char **. (Компилятор должен пожаловаться.) В строке 4 мы присваиваем const char * a const char *; это явно законно. В строке 5 мы модифицируем то, на что указывает char * - это должно быть законно. Тем не менее, p1 в конечном итоге указывает на c, то есть const. Это произошло в строке 4, потому что *p2 было действительно p1. Это было установлено в строке 3, которая является назначением запрещенной формы, и именно поэтому строка 3 запрещена.

И поскольку ваш вопрос помечен как C ++, а не как C, он даже объясняет, какие const квалификаторы использовать вместо:

(C ++ имеет более сложные правила для назначения указателей, соответствующих константам, которые позволяют выполнять больше видов назначений без появления предупреждений, но при этом защищают от непреднамеренных попыток изменить значения констант. C ++ по-прежнему не позволяет назначать char ** для const char **, но это позволит вам присвоить char ** для const char * const *.)

11 голосов
/ 08 февраля 2010

Так как никто не опубликовал решение , здесь:

char *s1 = 0;
const char *s2 = s1; // OK...
char *a[MAX]; // aka char **
const char * const*ps = a; // no error!

(http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.17 почему)

9 голосов
/ 24 марта 2015

Проект стандарта C ++ 11 объясняет это в примечании в разделе 4.4, в котором говорится:

[Примечание: если программа может назначить указатель типа T ** указателю типа const T ** (то есть, если строка # 1 ниже была разрешена), программа может непреднамеренно изменить объект const (как это делается в строке # 2). Например,

int main() {
const char c = 'c';
char* pc;
const char** pcc = &pc; // #1: not allowed
*pcc = &c;
*pc = 'C'; // #2: modifies a const object
}

- Конечная заметка]

Интересный связанный вопрос: Учитывая, что int ** p1 и const int ** p2 - это p1 == p2, хорошо сформированный?

Обратите внимание, что

C ++ FAQ также имеет объяснение этому, но мне больше нравится объяснение из стандарта.

Соответствующий текст, сопровождающий примечание, выглядит следующим образом:

Конверсия может добавить квалификаторы cv на уровнях, отличных от первого в многоуровневые указатели при соблюдении следующих правил: 56

Два типа указателя T1 и T2 похожи, если существует тип T и целое число n> 0 такое, что:

T1 - указатель cv1,0 на указатель cv1,1 на · · · cv1, n-1 указатель на cv1, n T

и

T2 - указатель cv2,0 на указатель cv2,1 на · · · cv2, n − 1 указатель на cv2, n T

где каждый cvi, j является const, volatile, const volatile или ничего. n-кортеж cv-квалификаторов после первого в типе указателя, например, cv1,1, cv1,2, ..., cv1, n в типе указателя T1, называется cv-квалификационная подпись типа указателя. Выражение типа T1 может быть преобразован в тип T2 тогда и только тогда, когда выполнены следующие условия удовлетворены:

  • типы указателей похожи.
  • для каждого j> 0, если const в cv1, j, то const в cv2, j, и аналогично для volatile.
  • если cv1, j и cv2, j различны, то const в каждом cv2, k для 0
6 голосов
/ 26 июня 2014

Здесь есть два правила:

  • Не существует неявных приведений между T* и U*, если T и U являются разными типами.
  • Вы можете неявно разыграть T* до T const *. («Указатель на T» может быть приведен к «Указатель на const T»). В C ++, если T также является указателем, то это правило также может быть применено к нему (связывание).

Так, например:

char** означает: указатель на указатель на символ .

И const char** означает: указатель на указатель на const char .

Поскольку указатель на char и указатель на const char - это разные типы, которые отличаются не только по константности, поэтому приведение не допускается. Правильный тип для приведения должен быть константный указатель на символ .

Таким образом, чтобы сохранить правильность const, необходимо добавить ключевое слово const, начиная с крайней правой звездочки.

Таким образом, char** можно привести к char * const *, а также к const char * const *.

Эта цепочка только для C ++. В C эта цепочка не работает, поэтому на этом языке вы не можете правильно разыграть более одного уровня указателей const.

...