Какой смысл в константных указателях? - PullRequest
145 голосов
/ 10 октября 2011

Я говорю не об указателях на константные значения, а о самих указателях констант.

Я изучаю C и C ++ за пределами самых простых вещей, и до сегодняшнего дня я понял, что указатели передаются по значениюфункции, что имеет смысл.Это означает, что внутри функции я могу заставить скопированный указатель указывать на какое-то другое значение, не влияя на исходный указатель от вызывающей стороны.

Так какой смысл иметь заголовок функции, который говорит:

void foo(int* const ptr);

Внутри такой функции вы не можете заставить ptr указывать на что-то другое, потому что оно const и вы не хотите, чтобы оно было изменено, но функция, подобная этой:

void foo(int* ptr);

Работает так же хорошо!потому что указатель копируется в любом случае, и указатель в вызывающей стороне не затрагивается, даже если вы измените копию.Так в чем же преимущество const?

Ответы [ 17 ]

5 голосов
/ 10 октября 2011

int iVal = 10; int * const ipPtr = & iVal;

Как и обычная переменная const, указатель const должен быть инициализирован значением после объявления, и его значение нельзя изменить.

Это означает, что указатель const всегда будет указывать на одно и то же значение. В приведенном выше случае ipPtr всегда будет указывать на адрес iVal. Однако, поскольку значение, на которое указывает указатель, все еще не является константным, можно изменить значение, на которое указывает указатель, путем разыменования указателя:

* ipPtr = 6; // разрешено, поскольку pnPtr указывает на неконстантный тип int

4 голосов
/ 10 октября 2011

Пример того, где указатель const очень применим, может быть продемонстрирован таким образом.Предположим, у вас есть класс с динамическим массивом внутри, и вы хотите передать пользователю доступ к массиву, но не предоставляя им права на изменение указателя.Рассмотрим:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

Что дает:

Входные данные
Положим данные

Но если мы попробуем это:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

Получаем:

ошибка: в качестве левого операнда присваивания требуется lvalue // Снова произошла ошибка!

Очевидно, что мы можем изменять содержимое массива, но не указатель массива.Хорошо, если вы хотите убедиться, что указатель имеет согласованное состояние при передаче его обратно пользователю.Однако есть одна загвоздка:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

Мы все еще можем удалить ссылку на указатель в памяти, даже если мы не можем изменить сам указатель.

Так что, если вы хотите, чтобы ссылка на памятьвсегда указывайте на что-то (IE никогда не будет изменен, подобно тому, как в настоящее время работает ссылка), тогда это очень применимо.Если вы хотите, чтобы пользователь имел полный доступ и изменил его, то для вас неконстантное.

Редактировать:

После того, как вы отметили комментарий okorz001 о невозможностиназначить из-за того, что GetArray () является операндом с правильным значением, его комментарий полностью корректен, но вышеприведенное все еще применимо, если вы должны вернуть ссылку на указатель (я полагаю, я предполагал, что GetArray ссылался на ссылку), например:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

Возвращается в первом, что приводит к ошибке:

ошибка: назначение места только для чтения 'Temp.TestA :: GetArray ()'

Но второе произойдет весело, несмотря на потенциальные последствия для нижнего слоя.

Очевидно, возникнет вопрос: «Почему вы хотите вернуть ссылку на указатель»?В редких случаях вам нужно назначить память (или данные) непосредственно исходному указателю (например, создать свой собственный malloc / free или new / free front-end), но в этих случаях это неконстантная ссылка,Ссылка на константный указатель Я не сталкивался с ситуацией, которая оправдывала бы это (разве может быть объявленной константной ссылочной переменной, а не типом возврата?).

Рассмотрим, есть ли у нас функция, которая принимает константный указатель(в отличие от того, который этого не делает):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

Ошибка в const выдает сообщение:

Ошибка: уменьшение параметра только для чтения «Данные»

Это хорошо, так как мы, вероятно, не хотим этого делать, если только мы не хотим вызвать проблемы, обозначенные в комментариях.Если мы отредактируем декремент в функции const, произойдет следующее:

NonConst: A
Const: B

Понятно, даже если A - это «Данные»[1] ', он обрабатывается как' Data [0] ', потому что указатель NonConst разрешил операцию уменьшения.Реализуя const, как пишет другой человек, мы улавливаем потенциальную ошибку до того, как она возникнет.

Еще одно основное соображение заключается в том, что указатель const можно использовать в качестве псевдо-ссылки, то есть, что указывает на ссылкуне может быть изменено (интересно, если это было так, как это было реализовано).Обратите внимание:

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

При попытке компиляции выдает следующую ошибку:

Ошибка: назначение переменной только для чтения 'B'

Что, вероятно, плохо, если была нужна постоянная ссылка на А.Если B = NULL закомментировано, компилятор с радостью разрешит нам изменить *B и, следовательно, A. Это может показаться бесполезным для int, но подумайте, если у вас была единственная позиция графического приложения, где вы хотели использовать неизменяемый указатель, который ссылается нак нему, что вы могли бы обойти.

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

4 голосов
/ 10 октября 2011

Передача const-указателя на функцию не имеет большого смысла, так как в любом случае она будет передаваться по значению. Это всего лишь одна из тех вещей, которые допускаются общим языковым дизайном. Запрещение этого только потому, что это не имеет смысла, просто сделало бы спецификацию языка. больше.

Если вы находитесь внутри функции, это, конечно, другой случай. Наличие указателя, который не может изменить то, на что он указывает, является утверждением, которое делает код более понятным.

4 голосов
/ 10 октября 2011

Полагаю, преимущество было бы в том, что компилятор может выполнять более агрессивные оптимизации внутри функции, зная, что этот указатель не может измениться.

Это также позволяет избежать, например. передача этого указателя в подфункцию, которая принимает неконстантную ссылку на указатель (и поэтому может изменить указатель как void f(int *&p)), но я согласен, что полезность в этом случае несколько ограничена.

3 голосов
/ 12 октября 2011

Типы объявления любых переменных, таких как - (1) Объявление постоянной переменной. DataType const varibleName;<br/> <pre> int const x; x=4; //you can assign its value only One time</pre> (2) Объявить указатель на постоянную переменную const dataType* PointerVaribleName=&X;<br/> <pre> const int* ptr = &X; //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified</pre> dataType* const PointerVaribleName=&X;<br/> <pre> int* const ptr = &X; //Here pointer variable itself is constant Such that value of 'X' can be modified But pointer can't be modified</pre>

3 голосов
/ 10 октября 2011

Я полагаю, что это предотвратит увеличение или уменьшение указателя кода в теле функции.

3 голосов
/ 10 октября 2011

Нет ничего особенного в указателях, где вы бы никогда не хотели, чтобы они были постоянными. Точно так же, как у вас могут быть значения констант члена класса int, вы также можете иметь постоянные указатели по аналогичным причинам: вы хотите быть уверенными, что никто никогда не изменит то, на что указывают. Ссылки на C ++ несколько решают эту проблему, но поведение указателя унаследовано от C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...