Постоянный и неконстантный доступ разрешает различные перегрузки? - PullRequest
0 голосов
/ 13 октября 2010

Позвольте мне сказать, что у нас есть простая задача программирования. Но для ясности я начну с нескольких примеров кода. Прежде всего, мы написали некоторый класс контейнера данных, но для целей задачи, независимо от того, что это за класс. Нам просто нужно, чтобы он вел себя правильно-константно.

class DataComponent {
public:
    const std::string& getCaption() const {
        return caption;
    }

    void setCaption(const std::string& s) {
        caption = s;
    }

private:
    std::string caption;
};

Тогда давайте предположим, что у нас есть универсальный класс, который ведет себя как фасад над произвольным инкапсулированным экземпляром класса. Скажем, мы перегружены оператором доступа участника (->).

template <typename T> class Component {
public:
    Component() { instance = new T(); }

    ...

    const T* operator-> () const {
        return instance;
    }

    T* operator-> () {
        // but there might be additional magic
        return instance;
    }

private:
    T *instance;
};

В этот момент я должен сказать, как я хочу, чтобы это работало:

  • если мы вызываем неконстантные функции-члены базового класса через оператор доступа к члену (component->setCaption("foo")), компилятор рассматривает неконстантные T* operator-> () как лучший выбор.
  • в противном случае, если мы пытаемся вызывать константные функции-члены базового класса таким же образом (component->getCaption()), компиляторы выбирают const T* operator-> () const с другой стороны.

Этот пример кода выше не будет работать таким образом, поэтому мне интересно, есть ли возможность дать компилятору такое поведение, о котором я упоминал. Любые предложения.


РЕДАКТИРОВАТЬ: Пусть наш оператор доступа члена перегружен таким образом:

    const T* operator-> () const { return instance; }

    T* operator-> () {
        cout << "something going change" << endl;
        return instance;
    }

И давайте где-нибудь переменную Component<DataComponent> c. Затем при вызове c->getCaption() stdout следует хранить молчание, но при вызове c->setCaption("foo") stdout должен предупредить нас о том, что что-то изменится. VS 2010 compilier заставляет stdout предупреждать нас о каждом из этих вызовов.

Я понимаю, что такая семантика предполагает, что c ведет себя как const и не-const одновременно. Но любопытство все еще в моей голове.

1 Ответ

3 голосов
/ 13 октября 2010

Независимо от того, вызывается ли константный или неконстантный элемент, определяется исключительно константой объекта, для которого он вызывается, а не какой-либо последующей операцией. Это определение делается перед рассмотрением конкретного метода, который вы вызываете в DataComponent. Вы все еще могли бы взломать требуемую функциональность менее напрямую, используя прокси-объект вокруг DataComponent, с const и не- const пересылкой getCaption() с.

РЕДАКТИРОВАТЬ: детали в соответствии с просьбой (с макушки головы). Вам нужно будет заранее объявить некоторые из этих вещей - я не стал беспокоиться, поскольку это делает их еще более запутанными. Сделайте чип с любыми проблемами / обратной связью. Обратите внимание, что это в основном предполагает, что вы не можете / не хотите модифицировать Компонент по какой-то причине, но это не универсальное шаблонное решение, которое можно просто обернуть вокруг произвольного типа - оно очень сильно связано и требует больших затрат на обслуживание.

// know they can't call a non-const operation on T, so this is ok...
const T* Component::operator->() const { return instance; }

// they might invoke a non-const operation on T, so...
DataComponent::Proxy Component::operator->() { return DataComponent.getProxy(*this); }

in class DataComponent:

struct Proxy
{
    Component& c_;
    DataComponent& d_;

    Proxy(Component& c, DataComponent& d) : c_(c), d_(d) { }

    const std::string& get_caption() const { return d_.get_caption(); }

    void set_caption(const std::string& s)
    {
        c_.on_pre_mutator(d_);
        d_.set_caption(s);
        c_.on_post_mutator(d_);
    }
};

тогда

DataComponent::Proxy DataComponent::getProxy(Component& c) { return Proxy(c, *this); }

Итак, это означает, что где-то вам приходится вручную кодировать функции пересылки. Это боль, но если вы делаете это для отладки или тестирования, это не лишено смысла. Если вы делаете это, чтобы добавить блокировку или что-то в этом роде, то, вероятно, есть лучшие альтернативы.

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