[gcc] obsługa słowa kluczowego __property z BCC / VC

0

W C++ Builderze można dla właściwości klasy zdefiniować metody dostępowe, tzn. funkcje służące do zapisu i odczytu zmiennej:

int GetValue() { ... }
void SetValue(int) { ... }
__property int Value = { read = GetValue, write=SetValue };

W Microsoft Visual Studio też coś takiego jest, tylko składnia jest troszkę inna. Standard ISO C++ czegoś takiego nie przewiduje. Przeglądałem dokumentację GCC, ale też czegoś takiego nie widziałem w rozszerzeniach.

Czy może jednak jest coś takiego? A może jest jakaś biblioteka wprowadzająca taki mechanizm metodą kombinowaną?

Dopisane:
tytuł zmieniłem, bo może enigmatyczny był :] ale nie o tym chciałem:

Zrobiłem rozwiązanie kombinowane, które ma jednak dużą wadę - na każdą właściwość klasy powstaje narzut 4 bajtów. Postaram się wyjaśnić, jak to wymodziłem - może ktoś będzie widział, co trzeba w tym rozumowaniu zmienić, żeby pozbyć się tego narzutu...

Aby zmienna reagowała na zapis / odczyt opakowałem ją w klasę i przeładowałem operator konwersji i przypisania. Później się zastanowię nad szczegółami przeładowania odpowiednio reszty potrzebnych:

class Opakowanie {
   private:
      int value;
   public:
   int operator=(int v) { cout << "set"; return value=v; }
   operator int() { cout << "get"; return value; }   
   };

Ale ja chcę, żeby klasa wywoływała zewnętrzne funkcje dostępowe. sama zmienna value też lepiej, żeby była na zewnątrz. Tzn. mam komponent właściwy i w nim wszystko się znajduje. Posiada on opakowaną właściwość. I ma ona korzystać nie ze swoich jakichś funkcji, ale właśnie z metod i zmiennych komponentu.

wymyśliłem coś takiego:

istnieje wskaźnik do składowej obiektu. Można nim inicjalizować szablon - a więc już w trakcie deklaracji mogę określić funkcje dostępowe. Rozrasta mi się sekcja kodu, ale to jeszcze nie powoduje narzutu na każdy obiekt danej klasy.

Szablon działa na zasadzie Opakowania powyżej, tzn ma operatory konwersji i przypisania. Wewnątrz nich wywołuje odpowiednie metody używając otrzymanych wskaźników. I tu mam problem: wskaźnik do składowej może być użyty tylko w połączeniu z obiektem/jego adresem.

Wskutek tego każdy szablon zawiera wskaźnik owner, którego używa się do wywołania metod dostępowych. Wskaźnik ten jest inicjalizowany podczas konstrukcji wskaźnikiem this komponentu.


class TJakisKomponent {

   protected:
      // "surowa" wartość i metody dostępowe
      int value;

      void setValue(int v) { cout << "set"; value=v; }
      int getValue() { cout << "get"; return value; }

   public:
      // wskaźniki do metod dostępowych
      typedef void (TJakisKomponent ::*pset)(int);
      typedef int (TJakisKomponent ::*pget)();


      // klasa szablonowa pobierająca adresy metod dostępowych
      // nie uzależniam od typu właściwości na razie, żeby nie zaciemniać do końca
      template<pset set, pget, get> class TAccessor {
         private:
            // to jest narzut - właściwość musi znać adres obiektu, a do this "TJakisKomponent " nie dojdzie
            TJakisKomponent * owner;

         public:
            // podczas konstrukcji trzeba inicjalizować właściwość
            TAccessor(TJakisObiekt* o) : owner(o) {}
            //użycie wskaźnika do funkcji skł. klasy (tu jest właśnie potrzebny adres obiektu, bez niego wskaźnik jest bezużyteczny)
            void operator=(int value) {  (owner ->* set)(value);  }
            // j.w.
            void operator int() { return (owner ->* get)(); }
         };

       // przyjaźń, żeby mógł używać metod chronionych dostępowych:
       template<class,class> friend class TAccessor;

       // po tej walce można wreszcie dać właściwości :)
       TAccessor < &TJakisObiekt::setValue, &TJakisObiekt::getValue > Value;
   };

po wykombinowaniu kilku makr i pewnych modyfikacjach ma to nawet znośną składnię, więc gdyby nie ten narzut, to byłbym zadowolony (sekcja kodu to mi może rosnąć w nieskończoność :] ):

class TArea {
    private:
        int value;
        string name;

    protected:
        virtual int getValue();
        virtual void setValue(int w);
        virtual string getName();
        virtual void setName(const string& n);

    public:
        #define useAccessors TArea
        __use_accessor(int);
        __use_fast_accessor(string);

        __accessor (int, Value);
        __accessor (string, Name);
    };

Wskutek całego tego wywodu aktualizuje się trochę lista pytań:

  1. czy GCC ma jakąś normalniejszą możliwość obsługi właściwości
  2. czy może jest jakaś biblioteka, która to robi normalniej
  3. czy może da się to jakoś zmodyfikować, żeby normalniej
0

pliizzz, jak nie ma niczego innego, to niech to ktoś przynajmniej powie, bo chyba bym się pochlastał, jak bym miał zarwać następne x dni na myślenie o tym, a później dostałbym linka do 10x efektywniejszego rozwiązania :)

0

Skoro ci ten narzut 4-bajtowy tak strasznie przeszkadza, jest sposób na wyeliminowanie go.
Otóż wywołując jakąś metodę TAccesor'a dysponujesz wskaźnikiem this do niego - wystarczy odjąć odpowiedni offset i mamy wskaźnik this właściciela TAccesor'a.
Można też wyeliminować __use_accessor definiując klase szablonową TAccesor poza klasą komponentu (właściwie byłaby to pusta klasa posiadająca tylko parametry szablonu), a wewnątz komponentu umieścić deklaracje klasy nieszablonowej dziedziczącej po TAccesor, w której są zdefinowane operatory.

Działająca przeróbka twojego kodu:

class TJakisKomponent {
   protected:
      int value;
      void setValue(int v) { cout << "set"; value=v; }
      int getValue() { cout << "get"; return value; }
   public:
      TJakisKomponent() {};
      typedef void (TJakisKomponent ::*pset)(int);
      typedef int (TJakisKomponent ::*pget)();
      template<pset set, pget get> class TAccessor {
         public:
            void operator=(int value) {
               //magiczna linijka:
               (((TJakisKomponent*) (((int) this) - (int) &(((TJakisKomponent*) NULL)->Value)))  ->* set)(value);  
            }
            operator int() { return (((TJakisKomponent*) (((int) this) - (int) &(((TJakisKomponent*) NULL)->Value))) ->* get)(); }
         };
       friend class TAccessor< &TJakisKomponent::setValue, &TJakisKomponent::getValue >;
       TAccessor < &TJakisKomponent::setValue, &TJakisKomponent::getValue > Value;
   };
0

dzięki wielkie, świetne, o takim policzeniu offsetu:
&(((TJakisKomponent*) NULL)->Value))) ->* set)
nie pomyślałem. Dla gcc robiłem to ostrą i nieprzenośną akrobatyką, a to można przecież tak ładnie rozwiązać.
:)

0

Ja od zawsze używam g++ i generalnie nie rozumiem po co komu to property...

class Abc {
public:
void set(int new_value) { i = new_value; }
int get() const { return i; }

private:
int i;
}

Typowy przykład użycia funkcji inline w klasach...

Nie wiem, może coś pominąłem, jeśli tak to mnie poprawcie, bo to __property wygląda mi na kolejny nieprzenośny bajer :P

0

Cały bajer polega na skróceniu zapisu i niejako podniesieniu poziomu abstrakcji np. zamiast

kredka.setDlugosc(kredka.getDlugosc() - 1);

pisac

kredka.Dlugosc -= 1;

Ranides, podziel sie gotowym kodem jak skończysz. Kiedyś też próbowałem pisać coś podobnego, ale stwierdziłem, że szkoda zachodu :-P bo nie mogłem zaimplementować jakoś elegancko żeby akcesory get/set mogły być inline.

0

Zamieszczę, jak pracę skończę (czyli pewnie gdzieś nad ranem ;)) i do domu wrócę, żeby to uporządkować.

0
adf88 napisał(a)

Cały bajer polega na skróceniu zapisu i niejako podniesieniu poziomu abstrakcji np. zamiast

kredka.setDlugosc(kredka.getDlugosc() - 1);

pisac

kredka.Dlugosc -= 1;

Ale można równie dobrze w getDlugosc zwracać referencje, więc wyglądało by to tak:

kredka.getDlugosc() -= 1
0

jak zwracasz referencję, to tracisz 50% możliwości. Bo efekty uboczne przy odczycie możesz zaprogramować, ale efektów przy zapisie już nie (bo za pomocą referencji modyfikujesz bezpośrednio zmienną). Powiem więcej - zwracając referencję traci się kontrolę nad stanem obiektu, bo Bóg jeden wie, co tam użytkownik wsadzi... ;)

A czasem przydaje się kontrola i wygoda naraz. Oczywiście, to taki sam bajer jak przeładowanie operatorów na przykład. Tylko ułatwienie składniowe.

0

hmm.. szcerze mowiac tez takie cos probowalem osiagnac z ~2 miesiace temu.. moze jeszcze mi sie cos zachowalo na dysku.. ogolnie moje rozwiazanie wygladalo orientacyjnie mniejwiecej tak:

  • template PropertyBase<T> gdzie T to typ wartosci, utrzymuje wewnatrz pole protected typu T, definiuje 2 konstruktory - domyslny () i (T const &) do inicjalizacji

  • template ReadableProperty<T, Getter>:public virtual PropertyBase<T> - konstruktory (Getter ) oraz (T const&, Getter ), template friend class Getter;

  • template Getter<T> to funktor z T & operator()(T & )

  • template WriteableProperty<T, Setter>:public virtual PropertyBase<T> - konstruktory (Setter ) oraz (T const&, Setter ), template friend class Setter;

  • Setter to funktor z T & operator()(T & )

  • template Property<T, Getter, Setter> : pubic ReadableP<T,G>, public WriteableP<T, S> i tutaj juz nic wiecej

idea dzialania byla taka, ze getter i setter nie sa raczej 'filtrami', tzn readableproperty wystawialo operatory konwersji na T const korzystajacy ktory defacto robil: return getter(this->pole), setter zas this->pole = setter(argument); dzieki temu Getter/Setter nie musialy utrzymywac & na propertybase zeby sie dostac do propertybase::pole. no i oczywiscie getter/setter mogly od razu zwrocic wartosc, mogly ja zmodyfikowac i dopiero zwrocic, czy tez rzucic wyjatkiem get/setError itp przerywajac tym samym operacje zanim zajdzie odczyt/zmiana. na dobra sprawe getter i setter to byl prawie ten sam szablon, moze paroma constami sie roznil..

Ranides - zwracanie przez referencje - no to wlasnie wtedy mozna tak jak napisalem - zwracac obiekt proxy (u mnie: writeableproperty) ktory ma operator=(T&) i ktory wewnatrz decyduje co robic i ewentualnie ustawia to wlasciwe pole

1 użytkowników online, w tym zalogowanych: 0, gości: 1