Обнаружил, что есть аспект работы C++, о котором я раньше как-то не задумывался. А именно: если у вас есть две реализации одного метода (перегрузка), отличающихся константностью *this:
int & v();
const int & v() const;
когда и какой метод будет вызываться?
Собственно, работа первого теста ясна, мы к ней вернёмся позже. В данном контексте интересней работа второго теста. Видно, что при выборе const/не-const варианта метода v(), С++ учитывает не константность требуемого результата, а константность объекта для которого вызывается метод.
Если задуматься, то такое поведение оказывается вполне логичным. Дело в том, что в сигнатуру метода не входит константность возвращаемого значения, за-то входит константность *this. Таким образом, в момент вызова, возвращаемое значение не определяет, какой из методов будет вызываться. За-то компилятор знает, о константности x и у, то есть, он знает о константности *this для x и y, и он может именно по этому критерию выбрать модификацию метода v().
То есть наш класс выглядит так:
Что ж, наш код не скомпилится. Для работы с переменной y нам будет недоставать метода, для константного *this.
Тогда все присвоения (второй тест) будут работать отлично, и будут использовать константный метод. Но первый тест, конечно, работать не будет. Если вам не нужна такая функциональность (а она нужна далеко не всегда), то вы можете и не реализовывать не-const-методы.
Зачем это нужно решать вам :-) Но меня заставили об этом задуматься контейнеры Qt (многие другие контейнеры сделаны так же, просто как-то я до этого не обращал внимания на эту особенность). Например, контейнер QList имеет четыре метода доступа к элементу по индексу:
Среди них есть очень похоже (сравните первый и последний). После вышеприведённых раздумий мне стало ясно, зачем сделана такая избыточность, когда какой метод надо использовать и когда используется какой из перегруженных методов. Надеюсь, читателю теперь тоже это ясно.
Всем успехов!
int & v();
const int & v() const;
когда и какой метод будет вызываться?
Пускай есть оба метода: и const, и не-const
Сразу же боевой пример:#include <iostream><br><br>class A {<br>public:<br> int val;<br> A(int x): val(x) {}<br> int & v() {<br> std::cout << "v()" << std::endl;<br> return val;<br> }<br> const int & v() const {<br> std::cout << "v() const" << std::endl;<br> return val;<br> }<br>};<br><br>int main() {<br> std::cout << "test 1" << std::endl;<br> A x(1);<br> std::cout << "x.val = " << x.val << std::endl;<br> x.v() = 3;<br> std::cout << "x.val = " << x.val << std::endl;<br><br> std::cout << "test 2" << std::endl;<br> A const y(2);<br> int a = x.v();<br> int const b = x.v();<br> int c = y.v();<br> int const d = y.v();<br>}<br><br>* This source code was highlighted with Source Code Highlighter.
Оказывается он выдаёт:test 1 x.val = 1 v() x.val = 3 test 2 v() v() v() const v() const
Собственно, работа первого теста ясна, мы к ней вернёмся позже. В данном контексте интересней работа второго теста. Видно, что при выборе const/не-const варианта метода v(), С++ учитывает не константность требуемого результата, а константность объекта для которого вызывается метод.
Если задуматься, то такое поведение оказывается вполне логичным. Дело в том, что в сигнатуру метода не входит константность возвращаемого значения, за-то входит константность *this. Таким образом, в момент вызова, возвращаемое значение не определяет, какой из методов будет вызываться. За-то компилятор знает, о константности x и у, то есть, он знает о константности *this для x и y, и он может именно по этому критерию выбрать модификацию метода v().
А что будет, если перегрузки нет?
Что будет, если мы реализовали только не-const-метод?
То есть наш класс выглядит так:
class A {<br>public:<br> int val;<br> A(int x): val(x) {}<br> int & v() {<br> std::cout << "v()" << std::endl;<br> return val;<br> }<br>};
<br><br>* This source code was highlighted with Source Code Highlighter.
Что ж, наш код не скомпилится. Для работы с переменной y нам будет недоставать метода, для константного *this.
А если мы реализуем только const-метод?
class A {<br>public:<br> int val;<br> A(int x): val(x) {}<br> const int & v() const {<br> std::cout << "v()" << std::endl;<br> return val;<br> }<br>};<br><br>* This source code was highlighted with Source Code Highlighter.
Тогда все присвоения (второй тест) будут работать отлично, и будут использовать константный метод. Но первый тест, конечно, работать не будет. Если вам не нужна такая функциональность (а она нужна далеко не всегда), то вы можете и не реализовывать не-const-методы.
А зачем всё это нужно?
Зачем это нужно решать вам :-) Но меня заставили об этом задуматься контейнеры Qt (многие другие контейнеры сделаны так же, просто как-то я до этого не обращал внимания на эту особенность). Например, контейнер QList имеет четыре метода доступа к элементу по индексу:
const T & at (int i) const;<br>T value (int i) const;<br>T & operator[] (int i);<br>const T & operator[] (int i) const;<br><br>* This source code was highlighted with Source Code Highlighter.
Среди них есть очень похоже (сравните первый и последний). После вышеприведённых раздумий мне стало ясно, зачем сделана такая избыточность, когда какой метод надо использовать и когда используется какой из перегруженных методов. Надеюсь, читателю теперь тоже это ясно.
Всем успехов!