суббота, 25 июня 2011 г.

Property C++: Upgrade

Как я и думал, публикация идей на хабре – хорошая штука. В комментариях к предыдущей статье я нашёл несколько очень важных замечаний.
Первое, самое очевидное – никак не проверяется вызов init в конструкторе. То есть если программист забыл проинициализировать свойства, то компилятор ничего не скажет, а падение в рантаме в коде класса Property – весьма непонятная для дебага ошибка.
Выходом из данной ситуации может быть простейшая вещь – добавление модификатора const. Константные поля программист обязан инициализировать в конструкторе, поэтому компилятор не даст забыть про инициализацию свойств. Константность даже логична, ведь сам объект свойства не должен изменяться за время существования объекта, которому он принадлежит. Разумеется, добавление const к существующей реализации не решит проблемы – такой код даже не соберётся. Требуется написать методы для работы с константными свойствами. Так же для удобства использования определим макрос, который вместо Property будет подставлять const Poperty.
Второй существенный недостаток – отсутствие конструктора копирования свойства. Для того чтобы их реализовать надо понять, что должно копироваться: объект свойства или значение, которое можно получить через getter? Объект свойства целиком копировать нельзя как минимум из-за того, что нужно изменить указатель на владельца. Да и выше вроде как было решено сделать свойства константными. Геттер вообще может не существовать, как и сеттер. Из всего выше сказанного делаю вывод: конструктор копирования не нужен вообще. Копировать просто нечего и некуда: они константны, ничего измениться не сможет. Таким образом константность решает проблему описанную тут.
В итоге получаем код:
enum PropertyAccess
{
 ReadOnly,
 WriteOnly,
 ReadWrite
};
 
 
template <typename Type, typename Owner, PropertyAccess Access>
class MyProperty
{
 
};
 
template<typename Type, typename Owner> 
class MyProperty<typename Type, typename Owner, ReadWrite>
{
protected:
    typedef Type (Owner::*getter)();
    typedef void (Owner::*setter)(Type);
    Owner * m_owner;
    getter m_getter;
    setter m_setter;
public:
    // Оператор приведения типа. Реализует геттер.
    operator Type() const
    {
        return (m_owner->*m_getter)();
    }
    // Оператор присваивания. Реализует сеттер.
    void operator =(Type data) const
    {
        (m_owner->*m_setter)(data);
    }
 
    MyProperty(Owner * const owner, getter getmethod, setter setmethod) :
        m_owner(owner),
        m_getter(getmethod),
        m_setter(setmethod)
    {}
};
 
 
template<typename Type, typename Owner> 
class MyProperty<typename Type, typename Owner, ReadOnly>
{
protected:
    typedef Type (Owner::*getter)();
    Owner * m_owner;
    getter m_getter;
public:
    // Оператор приведения типа. Реализует геттер.
    operator Type() const
    {
        return (m_owner->*m_getter)();
    }
 
    MyProperty(Owner * const owner, getter getmethod) :
        m_owner(owner),
        m_getter(getmethod)        
    {
    }
};
 
 
 
template<typename Type, typename Owner> 
class MyProperty<typename Type, typename Owner, WriteOnly>
{
protected:
    typedef void (Owner::*setter)(Type);
    Owner * m_owner;
    setter m_setter;
public:
    // Оператор присваивания. Реализует сеттер.
    void operator =(Type data) const
    {
        (m_owner->*m_setter)(data);
    }
 
    MyProperty(Owner * const owner, setter setmethod) :
        m_owner(owner),
        m_setter(setmethod)
    {
    }
 
};
 
 
#define Property (const MyProperty)
Вот так всё просто: проблемы было две, решать начинал по-разному, а решение оказалось одно универсальное, при этом ещё и весьма логичное.




Новые исходники.
Проект Visual Studio 2008.

Комментариев нет:

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