Почему С++ не разрешает преобразование неконстантного в константное в copy ctor?

У меня есть два члена геттера:

Node* prev() { return prev_; }
int value()  { return value_ }

Обратите внимание на отсутствие константных идентификаторов (я их забыл, но теперь хочу знать, почему это не сработает). Я пытаюсь заставить это скомпилировать:

Node(Node const& other) : prev_(other.prev()), value_(other.value()) { }

Компилятор отвергает это. Я думал, что С++ допускает преобразование неконстантных в константные параметры функции, например:

{
   Foo(int bar);
}

Foo(const int bar)
{
   //lala
}

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


person jkeys    schedule 31.07.2009    source источник


Ответы (5)


Вы не пытаетесь сделать неконстантное преобразование в константное. Вы пытаетесь вызвать два метода, которые не являются константами и ссылкой на константу (вызовы prev и value). Этот тип операции строго запрещен константной семантикой.

Вместо этого вы могли бы использовать поля prev_ и value_ напрямую. Поскольку это член того же типа, вы можете получить доступ к приватным, которые будут доступны в объекте const.

person JaredPar    schedule 31.07.2009

Я думал, что С++ допускает преобразование неконстантных в константные параметры функции, например:

Вы пытаетесь сделать прямо противоположное: от константы к неконстанте. При вызове неконстантной функции-члена компилятор свяжет выражение (которое представляет собой Node const с Node& для привязки указателя this). Таким образом, он отбросит const - не разрешено, потому что он вызовет неконстантную функцию для константного объекта.

/* class Foo */ {
   Foo(int bar);
}

/* Foo:: */ Foo(const int bar)
{
   //lala
}

Ну этот код совсем другое дело. Он объявляет функцию (конструктор) два раза, с той лишь разницей, что один раз параметр константный, а другой раз нет. Тип функции в обоих случаях один и тот же, поэтому они не конфликтуют (параметр const будет влиять только на тело функции локально — для вызывающей стороны это не будет иметь никакого значения). Кроме того, этот код не содержит никаких вызовов (при условии, что первый блок находится внутри какой-то функции).

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

/* class Foo */ {
   Foo(int &bar);
}

// different thing, won't work!
/* Foo:: */ Foo(const int &bar)
{
   //lala
}
person Johannes Schaub - litb    schedule 31.07.2009

Вы правы - const означает, что вы обещаете ничего не менять. Компилятор заставляет вас сдержать свое обещание, не позволяя вам вызывать методы для этого объекта, которые сами по себе не обещают ничего не менять.

person Tyler McHenry    schedule 31.07.2009
comment
Таким образом, размещение const после круглых скобок сообщает моему копировщику, что они НЕ МОГУТ изменять чужие данные? - person jkeys; 31.07.2009
comment
дело не в конструкторе, а в методах доступа, которые не являются константами, поэтому их нельзя вызывать для константного объекта. - person Javier; 31.07.2009
comment
Так что мой компилятор просто параноик. Даже если мои методы доступа не мутируют данные, конструктор копирования не знает об этом? - person jkeys; 31.07.2009
comment
Это не паранойя, это стандарт C++. Методы не считаются константными, если только вы явно не укажете, что они являются константными, включив ключевое слово const. Да, для такого простого метода компилятор мог бы выполнить статический анализ, необходимый для определения того, что вы на самом деле ничего не меняете, но в стандарте C++ по-прежнему не разрешено вызывать неконстантный метод для константного объекта. - person Tyler McHenry; 31.07.2009
comment
Не ваш компилятор - сам язык. C++ не требует, чтобы ваш компилятор догадался, что вы забыли объявить метод доступа const только потому, что ваша текущая реализация (которая может находиться в другом модуле компиляции) не изменяет данные. - person VoiceOfUnreason; 31.07.2009
comment
Итак, вы знаете, причина, по которой существует это правило, заключается в том, что может быть (и часто так и бывает), что код для конструктора копирования и код для методов доступа находятся в разных модулях компиляции. Это означает, что при компиляции конструктора компилятор видит только прототип метода доступа и не видит код внутри него. Поэтому компилятор должен иметь возможность определить, будет ли вызов модифицироваться или нет, основываясь только на прототипе метода; отсюда и необходимость в квалификаторе const в прототипе метода. - person Tyler McHenry; 31.07.2009
comment
Кроме того, функция может быть преднамеренно неконстантной, потому что, хотя она в настоящее время не изменяет объект, она будет делать это в будущем или его часть интерфейса, где другие реализации могут видоизменяться. Тогда вы бы не хотели, чтобы компилятор все равно помогал вам вызывать его, позволяя вам писать код, который сломается, когда будут сделаны ожидаемые будущие изменения. - person Steve Jessop; 31.07.2009

В объявлении конструктора Node(Node const& other) аргумент other объявляется const. Это означает, что вы можете вызывать только const метода.

Вы вызываете other.prev() и other.value() из конструктора, оба из которых не являются методами const, поэтому компилятор жалуется.

person atomice    schedule 31.07.2009

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

Простое решение для этого - просто пометить ваши функции-получатели как const:

Node* prev() const { return prev_; }
int value()  const { return value_; }

поскольку они не изменяют объект; теперь их можно вызывать с помощью константного объекта. На самом деле это, вероятно, следует сделать в любом случае, чтобы пользователи класса могли вызывать эти геттеры с константными объектами.

Однако у вас есть еще одна потенциальная проблема, если ваш копировщик выглядит так:

Node(Node const& other) : prev_(other.prev()), value_(other.value()) { }

копия и оригинал будут указывать на один и тот же объект Node в своем элементе prev_. В зависимости от семантики класса это может быть нормально, но это может быть проблема владения или другое логическое несоответствие.

person Michael Burr    schedule 31.07.2009
comment
Хороший улов в вопросе собственности. - person David Thornley; 31.07.2009