C ++: эмуляция свойств объекта: мысли - PullRequest
1 голос
/ 08 февраля 2012

Всем доброго времени суток!

Свойства не реализованы в C ++.То есть мы не можем написать

myObject.property = value;   // try to set field f_ to value

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

Вместо этого мы должны написать код получателя / установщика:

myObject.setField(value);    // try to set field f_ to value

Этот пост о эмуляции свойств в C ++ .Вызов свойства выглядит как вызов общедоступного элемента данных, но для задания или получения реального значения частного элемента-члена используется пользовательский код UDF.

Что бы мы хотели сделать, это разрешить простое использование интерфейса (общедоступные данные)как член, без синтаксиса вызова функции), в то время как базовый метод получения / установки может быть сложным (делать больше, чем только назначение / чтение данных) .

Следующий код (мой коллега написал) представляет простую реализацию свойства:

#include <iostream>

template<class C, class M>
inline C* get_parent_this(typename M C::*member_ptr, M*const member_this) 
{
    //interpret 0 as address of parent object C and find address of its member
    char* base   = reinterpret_cast<char*>(nullptr);
    char* member = reinterpret_cast<char*>( &(reinterpret_cast<typename C*>(base)->*member_ptr) );
    //modify member_this with offset = (member - base)
    return reinterpret_cast<typename C*>(reinterpret_cast<char*>(member_this) - (member - base) );
}

class Owner
{
    int x,pr_y;

    int   get_y()       {return pr_y;}
    void  set_y(int v)  {pr_y = v;}
public:
    struct
    {
        operator int()          { return get_parent_this(&Owner::y, this)->get_y(); }
        void operator =(int v)  { get_parent_this(&Owner::y, this)->set_y(v); } 
    } y;
};

int main ()
{   
    Owner ow;
    ow.y = 5;
    std::cout << ow.y;

    if( get_parent_this(&Owner::y, &ow.y) == &ow) std::cout << "OK\n";
    if( (char *)&ow.y != (char *)&ow) std::cout << "OK\n";

    return 0;
}

В приведенном выше примере есть простая пара получатель / установщик, которая не выполняет никакой дополнительной работы (например, проверка целостности или проверка границ), но этот код может быть сложным при выполнении проверки границ, проверки целостности и т. Д.Это только тестовый пример.

Не беспокойтесь о "странном" коде в шаблоне помощника get_parent_this.Самая «ужасающая» вещь - это расчет смещения.Адрес nullptr (NULL, 0x0) используется в качестве заглушки.Мы не пишем и не читаем с этого адреса.Мы используем его только для вычисления смещения объекта владельца на основе адреса подобъекта .Мы можем использовать любой адрес вместо 0x0.Таким образом, это не имеет смысла.


Использование свойств:

  1. , если кто-то использует общедоступные свойства элемента данных, может быть полезно в случае: 1.1.отслеживать публичные вызовы членов данных, если что-то пойдет не так;1.2.обновить безболезненно устаревший код, основанный на использовании открытых элементов данных;

  1. Что вы думаете об эмуляции свойств в C ++?Это живая идея?Есть ли у этой идеи недостатки (покажите, пожалуйста)?
  2. Что вы думаете о расчете адреса объекта владельца по адресу объекта?Какие техники и возможные подводные камни вы знаете?

Пожалуйста, расскажите нам свои мысли!

Спасибо!

Ответы [ 2 ]

2 голосов
/ 08 февраля 2012

Код не компилируется по некоторым очевидным причинам.Большая часть typenameS не нужна.Я полагаю, nullptr - это какой-то домашний напиток #define (это точно не C ++ 11 nullptr).

Вот предварительно проверенная компилирующая версия, которая облегчает понимание того, что происходит на самом делеon:

#include <iostream>

template<class C, class M>
inline C* get_parent_this(M C::*member_ptr, M* const member_this) 
{
  C* base = NULL;
  // !!! this is the tricky bit !!!
  char* member = reinterpret_cast<char*>(&(base->*member_ptr));
  return reinterpret_cast<C*>(reinterpret_cast<char*>(member_this) - member );
}

class Owner
{
  int x, pr_y;
  virtual int   get_y()       {return pr_y;}
  void  set_y(int v)  {pr_y = v;}
public:
  struct
  {
    operator int()          { return get_parent_this(&Owner::y, this)->get_y(); }
    void operator =(int v)  { get_parent_this(&Owner::y, this)->set_y(v); } 
  } y;
};

Хитрый бит: это включает разыменование нулевого указателя.Это несколько похоже на то, как макрос offsetof в stddef.h был определен (и до сих пор) определен в некоторых компиляторах.На некоторых компиляторах это работает надежно, но поведение не определено.Вот несколько ссылок на обсуждения по теме:

Iне думаю, что стоит повторять обсуждение здесь.Я действительно не вижу, что код покупает вас, кроме запутанных публичных данных.В тех немногих случаях, когда вам действительно нужен setter, я бы просто написал его вместо того, чтобы использовать это.Если ваш стиль кодирования запрещает общедоступные данные по своеобразным причинам, напишите набор макросов, чтобы определить их.

1 голос
/ 08 февраля 2012

Ссылочные объекты - это уже банка червей. Проблемы включают в себя:

  • Они мешают выводу типа, например y = max(y, myobject.y)
  • Вы не можете перегрузить operator., поэтому проксирование такого класса довольно хлопотно
  • Сюрпризы рода swap(object1.y, object2.y).

То, как вы используете это, имеет дополнительные сюрпризы. Например, если кто-то хочет вызвать побочные эффекты вашего геттера, простое написание myobject.y; не сработает: на самом деле он должен вызвать приведение к int.

Я не могу представить, что вы получите какой-либо совет, кроме "не делайте этого", если вы не сможете дать хорошее объяснение того, почему вы думаете, что вам нужно подражать свойствам. (и даже тогда, скорее всего, совет будет наклонен к объяснению, почему он вам действительно не нужен)

...