Функция Const, вызывающая non const или наоборот (во избежание дублирования)?

Есть ли какие-либо преимущества, связанные друг с другом:

class Foo
{
public:
    const int& get() const
    {
        // stuff here
        return myInt;
    }

    int& get()
    {
        return const_cast<int&>(static_cast<const Foo*>(this)->get());
    }
};

или

class Foo
{
public:
    int& get()
    {
        // stuff here
        return myInt;
    }

    const int& get() const
    {
        return const_cast<Foo*>(this)->get();
    }
};

Я использовал только первый, но я только что увидел, что второй использовал его, поэтому мне интересно.

Комментарий // stuff here может быть нетривиальной проверкой, например, для получения индекса таблицы для возврата ссылки на элемент таблицы (например: myInt = myTable[myComputedIndex];), поэтому я не могу просто сделать ее общедоступной. Таким образом, таблица и любой член не являются константами.

Ответ 1

Если вам нужно создать функцию, которая является константной, и избегает дублирования, один простой способ сделать это делегирование реализации шаблону, например

class Foo {
private: 

    int my_int;
    template <typename ThisPtr>
    static auto& get(ThisPtr this_ptr) { 
        return this_ptr->my_int;
    }

public:
    int& get() {
        return get(this);
    }

    const int& get() const {
        return get(this);
    }
};

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

Ответ 2

Игнорируя вопрос о том, действительно ли вам нужен геттер, лучшее решение, когда дублирование функций как в методе const, так и не const заключается в том, чтобы метод non const вызывал метод const и отбросить const -ность результата (т.е. первую из двух альтернатив, которые вы представляете в вопросе).

Причина проста: если вы делаете это наоборот (с логикой в ​​методе не const), вы можете случайно изменить объект const, и компилятор не поймает его во время компиляции (поскольку метод не объявлен const) - это будет иметь поведение undefined.

Конечно, это только проблема, если "getter" на самом деле не является getter (т.е. если он делает что-то более сложное, чем просто возвращает ссылку на частное поле).

Кроме того, если вы не ограничены С++ 11, решение на основе шаблонов, представленное Curious в их ответе, является еще одним способом избежать этой проблемы.

Ответ 3

Есть ли какое-либо преимущество, использующее один над другим:...

Нет, оба плохо, потому что они нарушают принцип инкапсуляции данных.

В вашем примере вы должны сделать myInt публичным членом. Нет никакого преимущества, чтобы иметь геттеры для такого случая вообще.

Если вы действительно хотите (нужны) функции getter и setter, они должны выглядеть так:

class Foo
{
private: 
    mutable int myInt_;
 // ^^^^^^^ Allows lazy initialization from within the const getter,
 //         simply omit that if you dont need it.

public:
    void myInt(int value)
    {
        // Do other stuff ...
        myInt = value;
        // Do more stuff ...
    }

    const int& myInt() const
    {
        // Do other stuff ...
        return myInt_;
    }
}

Ответ 4

Вы не говорите, откуда приходит myInt, от этого зависит лучший ответ. Возможны 2 + 1 возможных сценария:

1) Наиболее распространенным случаем является то, что myInt происходит от указателя, внутреннего к классу.

Предполагая, что это лучшее решение, которое позволяет избежать дублирования кода и кастинга.

class Foo{
    int* myIntP;
    ... 
    int& get_impl() const{
       ... lots of code
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}
};

Этот пример выше относится к массиву указателей и (большинству) интеллектуальным указателям.

2) Другой распространенный случай: myInt ссылка или элемент значения, тогда предыдущее решение не работает. Но это также случай, когда a getter вообще не требуется. Не используйте геттер в этом случае.

class Foo{
     public:
     int myInt; // or int& myInt;
};

сделано!:)

3) Существует третий сценарий, обозначенный @Aconcagua, это случай внутреннего фиксированного массива. В этом случае это подтасовка, это действительно зависит от того, что вы делаете, если найти индекс действительно проблема, то это можно учесть. Однако неясно, каково применение:

class Foo{
    int myInts[32];
    ... 
    int complicated_index() const{...long code...}
public:
    int& get(){return myInts[complicated_index()];}
    const int& get() const{return myInts[complicated_index()];}
};

Моя точка зрения, понять проблему и не заниматься инженером. const_cast или шаблоны не нужны для решения этой проблемы.


полный рабочий код ниже:

class Foo{
    int* myIntP;
    int& get_impl() const{
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}

    Foo() : myIntP(new int(0)){}
    ~Foo(){delete myIntP;}
};

#include<cassert>

int main(){
    Foo f1; 
    f1.get() = 5;
    assert( f1.get() == 5 );

    Foo const f2;
//    f2.get() = 5; // compile error
    assert( f2.get() == 0 );    
    return 0;
}

Ответ 5

По мере того, как вы намереваетесь получить доступ к более сложным внутренним структурам (как это разъясняется посредством вашего редактирования, например, предоставление operator[](size_t index) для внутренних массивов, как это делает std::vector), вам нужно убедиться, что вы не вызываете undefined путем изменения потенциального объекта const.

Риск этого выше во втором подходе:

int& get()
{
    // stuff here: if you modify the object, the compiler won't warn you!
    // but you will modify a const object, if the other getter is called on one!!!
    return myInt;
}

В первом варианте вы можете быть в безопасности (если вы не делаете const_cast здесь тоже, что сейчас действительно было бы плохо...), что является преимуществом такого подхода:

const int& get() const
{
    // stuff here: you cannot modify by accident...
    // if you try, the compiler will complain about
    return myInt;
}

Если вам действительно нужно изменить объект в неконстантном геттере, вы не можете иметь общую реализацию в любом случае...

Ответ 6

Модификация объекта const с помощью неконстантного пути доступа [...] приводит к поведению undefined.

(Источник: http://en.cppreference.com/w/cpp/language/const_cast)

Это означает, что первая версия может привести к поведению undefined, если myInt на самом деле является константным элементом Foo:

class Foo
{
    int const myInt;
public:
    const int& get() const
    {
        return myInt;
    }
    int& get()
    {
        return const_cast<int&>(static_cast<const Foo*>(this)->get());
    }
};
int main()
{
    Foo f;
    f.get() = 10; // this compiles, but it is undefined behavior
}

Вторая версия не будет компилироваться, потому что неконстантная версия get будет плохо сформирована:

class Foo
{
    int const myInt;
public:
    int& get()
    {
        return myInt;
        // this will not compile, you cannot return a const member
        // from a non-const member function
    }
    const int& get() const
    {
        return const_cast<Foo*>(this)->get();
    }
};
int main()
{
    Foo f;
    f.get() = 10;  // get() is ill-formed, so this does not compile
}

Эта версия на самом деле рекомендована Скоттом Мейерсом в Effective С++ в разделе "Избегайте дублирования" в функции "const и Non- const Member".