Pull to refresh

Тонкости перегрузки методов по константности *this

Reading time4 min
Views3.9K
dress11.jpg - image uploaded to PicamaticОбнаружил, что есть аспект работы C++, о котором я раньше как-то не задумывался. А именно: если у вас есть две реализации одного метода (перегрузка), отличающихся константностью *this:
    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.

Среди них есть очень похоже (сравните первый и последний). После вышеприведённых раздумий мне стало ясно, зачем сделана такая избыточность, когда какой метод надо использовать и когда используется какой из перегруженных методов. Надеюсь, читателю теперь тоже это ясно.

Всем успехов!
Tags:
Hubs:
+32
Comments39

Articles