вторник, 17 февраля 2009 г.

В поиске решения "Можно ли заставить компилятор проверять изменение принципов работы компонентов?"

Ранее я озадачился тем, можно ли как-то оформить контракты для асинхронных сообщений так, чтобы компилятор отслеживал их нарушения еще во время компиляции. Это еще не решение, но, возможно, один из намеков на то, как его можно получить.

Идею подсказал lomeo в одном из RSDN-овских флеймов. Суть в том, что если речь идет о синхронных вызовах, то можно потребовать, чтобы некий метод возвращал значение уникального типа. А этот тип нельзя было получить никак иначе, кроме как выполнив определенное действие.

Итак, вот суть задачи в переложении на синхронные вызовы. Есть некий интерфейс s_iface_t, с методом query_state. При обращении к этому методу у объекта типа mailslot_t должен быть вызван метод send_state_notification. Причем не у абы какого объекта mailslot_t, а только у того, который не меняется в течение всего времени жизни объекта, реализующего интерфейс s_iface_t.

Т.е. мы должны иметь что-то вроде (примеры написаны на C++ без учета проблем сборки мусора):

// Интерфейс почтового ящика.
class mailslot_t {
  public :
    virtual void send_state_notification() = 0;
};

class s_iface_t {
  public :
    virtual КАКОЙ-ТО-ТИП query_state() = 0;
};

class s_component_t : public s_iface_t {
  public :
    s_component_t( mailslot_t & m );

    virtual КАКОЙ-ТО-ТИП query_state();
};

И какой же тип должен возвращать s_iface_t::query_state()? Например, такой:

// Сигнал о том, что был задействован почтовый ящик,
// который не может измениться.
class nonchangeable_mailslot_used_signal_t {
  // Создавать экземпляр этого типа нужно очень хитро.
  // Поэтому его создание делегируется специальному
  // вспомогательному классу.
  friend class nonchangeable_mailslot_holder_t;

  // А конструктор поэтому закрыт.
  nonchangeable_mailslot_used_signal_t( mailslot_t & m ) {
    m.send_state_notification();
  }

public :
  // Зато деструктор открыт.
  ~nonchangeable_mailslot_used_signal_t() {}
};

Т.е. конструирование объекта nonchangeable_mailslot_used_signal_t невозможно без вызова send_state_notification. Т.о. мы гарантируем, что раз s_iface_t::query_state() вызывается, значит следует вызов send_state_notification у какого-то mailslot-а. Осталось только обеспечить, чтобы send_state_notification вызывался у того mailslot-а, который был нам передан в конструкторе. Это уже достигается реализацией класса nonchangeable_mailslot_holder_t:

class nonchangeable_mailslot_holder_t {
  // Поскольку это ссылка, то после инициализации она
  // измениться не может.
  mailslot_t & mailslot_;

protected :
  // Конструктор защищен, значит его может вызвать только
  // производный от nonchangeable_mailslot_holder_t класс.
  // А это значит, что производный класс должен получать
  // в конструкторе ссылку на mailslot.
  nonchangeable_mailslot_holder_t( mailslot_t & m )
    : mailslot_(m)
    {}

  // Этот метод так же защищен, значит его может вызвать
  // только производный класс.
  nonchangeable_mailslot_used_signal_t
  send_state_notify() {
    return nonchangeable_mailslot_used_signal_t( mailslot_ );
  }
};

Теперь все, что остается сделать - это унаследовать s_component_t от nonchangeable_mailslot_holder_t:

class s_component_t
  : public s_iface_t
  , protected nonchangeable_mailslot_holder_t {
public :
  s_component_t( mailslot_t & m )
    : nonchangeable_mailslot_holder_t(m)
    {}

  virtual nonchangeable_mailslot_used_signal_t
  query_state() {
    return send_state_notify();
  }
};

Решение далеко не идеальное и оставляет пользователю возможности для обхода контракта (например, можно сделать вспомогательный класс, унаследованный от nonchangeable_mailslot_holder_t и использовать его временные объекты в новых версиях s_component_t::query_state). Но все эти фокусы нужно делать осознано, а контракты в данной задаче были предназначены как раз для того, чтобы нельзя было их нарушить случайно (по незнанию или недосмотру).

Отправить комментарий