Если метод не изменяет параметр, почему я не могу передать его как const? - PullRequest
2 голосов
/ 19 ноября 2011

Я студент, который до сих пор не совсем получил постоянные параметры.Я понимаю, почему это не сработает:

#include <stddef.h>

struct Node {

    int value;
    Node* next;
};

Node* cons(const int value, const Node * next) {

    Node * tmp = new Node();
    tmp->value = value;
    tmp->next = next;

    return tmp;
}

int main () {

    const Node * k;
    k = cons(1, NULL);

    Node * p;

    p = cons(2, k);

}

Это потому, что я «отбрасываю» константу k, давая p ее адрес.

Я хотел обозначить параметр «const», чтобы сказать, что мой метод не изменит узел напрямую.Где, как будто я должен был передать (Node * next), я чувствовал бы, что нет никакой гарантии, что мой метод не разыменовывает этот указатель и не портит узел.Может быть, это глупо, поскольку нет никакой гарантии, что позже на const Node я передал указатель, который не будет изменен через новый указатель на него.Просто кажется странным, что: в минусах этого метода все, что я делаю, это нацеливаю новый указатель на «следующий» - я никогда не касался следующего, просто указывал - и все же этого достаточно, чтобы нарушить константу.

Может бытьЯ только что недооценил силу обещания "const".

Спасибо!

Ответы [ 4 ]

9 голосов
/ 19 ноября 2011

Проблема в том, что ваш next аргумент имеет тип const Node * (читай: указатель на постоянный объект Node), тогда как поле Node::next имеет тип Node * (указатель на неконстантныйNode object).

Поэтому при выполнении

tmp->next = next;

Вы пытаетесь заставить компилятор взять указатель на объект const Node и присвоить его указателю на неконстантный Node.Если бы это сработало, это был бы простой способ обойти константность, потому что вдруг люди могли бы изменить аргумент next просто разыменовав tmp->next.

3 голосов
/ 19 ноября 2011

Может быть, я просто недооценил силу обещания "const".

Итак, какое обещание вы дали?Функция берет копию указателя на Node и обещает не разрешать никаких изменений этого Node.Обещание касается не указателя, который был бы Node * const (и довольно бесполезный, поскольку он передается по значению, а const на этом уровне удаляется из сигнатуры), а об указателе, Node.

Чтобы привести пример из реальной жизни, учтите, что друг предложил поливать ваши растения, пока вы находитесь в отпуске, под обещанием (обычно подразумеваемым), что он не будет красть что-либо от вашегожилой дом.Теперь вы делаете копию ключа и отдаете его своему другу, который сам делает копию ключа и передает ее третьему лицу, которое никогда не обещало не грабить вас.Вы считаете, что ваш друг нарушил свое обещание?Насколько сильным было его обещание?

Приведенный выше код имеет точно такой же шаблон: cons обещает, что вы можете дать ему копию указателя на Node и что он не позволит этому бытьизмененный, но как только вы введете код, он копирует этот указатель на другой указатель, который не дает никаких обещаний относительно Node, а затем передает копию тому, кто может быть заинтересован.

2 голосов
/ 19 ноября 2011

Если T - любой тип, то есть два связанных указателя типа, T * и const T *.

Вы получаете указатели по своей природе, беря "адрес-"некоторый объект типа T.Тип указателя зависит от константности объекта:

T x;
const T y;

T * p1       = &x; // OK, &x is T*
// T * p2    = &y; // Error!
const T * q1 = &y; // OK, &y is const T *
const T * q2 = &x; // also fine

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

Вот что происходит с вашим k: это const T *T = Node), и мы присваиваем ему значение T *, что хорошо, так как мы просто игнорируем тот факт, чтомы могли бы мутировать T, если бы захотели.

[В реальной жизни у нас обычно есть постоянные ссылки , а не постоянные объекты, но вы можете относиться к ним по существу как к тем жевещь (поскольку ссылка - это просто псевдоним для объекта и функционально идентична самому объекту).]

Теперь вы можете путать все эти const с константойсам объект: так же, как раньше у нас были x и y, одна изменяемая и одна константа, мы можем применить ту же логику к самому типу указателя: давайте определим P = T * и Q = const T *.Тогда в приведенном выше коде все наши объекты были изменяемыми объектами P p1, p2; Q q1, q2;.То есть мы всегда можем изменить любой из указателей, чтобы он указывал куда-то еще: q2 = &z;.Нет проблем.Это то, что вы делаете с k при его переназначении.

Конечно, мы также можем объявить константу версии этих указателей: const P p3 = &x; const Q q3 = &y;.Эти не могут быть переназначены (в конце концов, будучи постоянными).Если вы изложите это в терминах T, как обычно делают люди, это звучит немного громко:

T       * p3 const = &x;
const T * q3 const = &y;

Я надеюсь, что это не создало больше путаницы, чем решило!

1 голос
/ 19 ноября 2011

Может быть, это глупо, поскольку тогда нет никакой гарантии, что позже на const Node я передал указатель, который не будет изменен через новый указатель на него. Просто кажется странным, что: в минусах этого метода все, что я делаю, это нацеливаю новый указатель на «следующий» - я никогда не трогал следующий, просто указывал - и все же этого достаточно, чтобы сломать константу.

Это не просто то, что вы указали, это то, что вы указали не const указателем. Если вы сделаете Node::entry a const Node*, все должно работать.

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

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

int main()
{
    const Node n = { 5, NULL };
    Node* p = cons(6, &n);
}

Неопределенное поведение - отбрасывать const на n (или на указатель на n), и cons должен это учитывать. Я должен также упомянуть, что я полагаю, что часть вашего предположения состоит в том, что вы понимаете, что ваша функция может изменить const Node*, но утверждаете, что, поскольку проблемное присвоение является последней строкой метода, компилятор должен распознать, что это на самом деле не делает ничего плохого. Я не знаю ни одного правила в C ++, которое гласит, что «X - это незаконно, если, конечно, вы не делаете это как последнюю строку функции». Назначение не допускается независимо от того, идет оно первым или последним в функции.

Вы можете получить желаемый дизайн, а также иметь правильность const. Например, вам разрешено перегружать cons, чтобы принимать либо const Node*, либо Node*, и создавать списки из const или не-1038 * элементов соответственно. Если бы я знал больше о том, что вы хотите сделать, я мог бы получить лучший ответ о том, как туда добраться.

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