C++ permissions : creating read-only variables

Archived bsnes development news, feature requests and bug reports. Forum is now located at http://board.byuu.org/
Locked
byuu

C++ permissions : creating read-only variables

Post by byuu »

So, one of the things sorely lacking in C++ is native support for permissions. Take *nix for instance, a file can be read-write to the owner, but read-only to others.

To work around that, the common answer is a get function, eg:

Code: Select all

class Foo {
  private: int value;
  public: int getValue() const { return value; }
};


Another alternative is to name the variable p_value, and the read-only function value(). I hate both of these: many times, it makes the most sense to use the same name for both the variable and the read function, eg cartridge.name.

I also don't want it to act like something it's not, eg requiring a special set() function to assign to the variable is out of the question.

I've come up with a workaround:

Code: Select all

template<typename C>
class propertyClass {
public:
  template<typename T>
  class property {
  public:
    const T& operator()() const { return value; }
    property() : value() {}
    property(const T& newValue) : value(newValue) {}

  protected:
    operator T&() { return value; }
    property& operator=(const T& newValue) { value = newValue; return *this; }
    T value;

    template<typename U> struct type_cast { typedef U type; };
    friend class type_cast<C>::type;
  };
};

class Foo : public propertyClass<Foo> {
public:
  property<bool> bar;
  Foo() { bar = true; } //this is valid
} foo;

int main() {
  bool temp = foo.bar();  //this is valid
  foo.bar = false;  //... but this will cause an error
}


This abuses an issue with the C++ standard: for whatever reason, the following is not valid:

Code: Select all

template<typename T> struct foo { friend class T; }


... but since compilers go out of their way to enforce it, it's very easy to trick them:

Code: Select all

template<typename U> struct type_cast { typedef U type; };
friend class type_cast<C>::type;


Another more legal alternative would be to #define PROPERTY(classname) and generate the property class inside a class definition.

But both of these methods have one obvious flaw: friends are not transitive. That is, no derived class can gain read-write access to a property defined in a base class, which is a problem for me as most of my processor classes use abstract base classes to define the interface.

So, I've run out of tricks here. Anyone think they can do something like the above, but with the ability to be inherited?
byuu

Post by byuu »

Not quite what I wanted (need set() to modify variables), but it does allow for proper inheritance, at least ...

And no compiler hacks / #defines / unnatural syntax.

Code: Select all

class property {
public:
  template<typename T> class container;

protected:
  template<typename T> container<T>& set(container<T>&, const T);

public:
  template<typename T>
  class container {
  public:
    const T& operator()() const { return value; }
    container() : value() {}
    container(const T newValue) : value(newValue) {}

  protected:
    friend container<T>& property::set<T>(container<T>&, const T);
    T value;
  };
};

template<typename T>
property::container<T>& property::set(property::container<T> &p, const T value) {
  p.value = value;
  return p;
}

class Cartridge : public property {
public:
  container<bool> sdd1;
  container<const char*> name;

  Cartridge() : sdd1(false), name("???") {
    set(name, "<none>");
  }
};

class Subcart : public Cartridge {
public:
  void set_name(const char *newName) { set(name, newName); }

  Subcart() {
    set(sdd1, true);
    set(name, "The Legend of Zelda");
  }
} cartridge;

int main() {
  printf("%d\n%s\n", cartridge.sdd1(), cartridge.name());
  cartridge.set_name("A Link to the Past");
  printf("%s\n", cartridge.name());
  return 0;
}
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

Deja-vu (done something very similar to this years ago, including the issue with friendship... did I give you my code a while back?). Two choices:

1) different names, everyone understands, no subtle problems, and you don't give C programmers more ammo as to why they hate C++.

2) complicated brittle template solution, but you give the language a good scolding and don't yield to this absolutely glaring omission that every single program would benefit greatly from.

Which you choose depends on your overall goals.
byuu

Post by byuu »

I don't recall your solution, but then I'm very forgetful :(

I'd be very interested if you still have it lying around. Otherwise, I think I've mostly got it.

1. is certainly the easier and more standard way to go, but the naming conventions for it just make me sick:
bool isdd1, psdd1, p_sdd1, rw_sdd1, w_sdd1; //hungarian vomit :(
bool sdd1() const { return isdd1; }

bool sdd1;
bool has_sdd1() const { return sdd1; }
const char *get_name() ...
bool is_bsx_cart() ... //no consistency with prefixes

I also really don't want to declare both a variable and a function for every single property. A #define could sort of work, but would override the current public/protected/private setting for the class. And if only $ were a standard C++0x marker instead of a common extension, heh. That would really screw with people :P

Code: Select all

#define PROPERTY(type, name) \
protected: type $##name; \
public: const type& name() const { return $##name; }


2. with my first idea gives me beautiful syntax, but lacks derived class inheritance. Why, oh why can't you override friendship intransience? :(
Transience is the whole point of object-oriented programming, ugh.

2. with the second idea is great for inheritance, but it's basically replacing the specialized get() functions with specialized set() functions. But on the plus side, the set() function is one scope higher, so you still keep the same variable names ... and the headache should be on the internals, not on the external classes accessing it.
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

If you have lots of information about the object, return a struct containing it:

Code: Select all

class Foo {
public:
    struct info_t
    {
        bool This;
        int that;
        double the_other;
    };

    info_t info() const;
};

void user( Foo const& foo )
{
    cout << "this: " << foo.info().This << '\n';
    cout << "that: " << foo.info().that << '\n';
    cout << "the_other: " << foo.info().the_other << '\n';
}

Unless this is called really often, efficiency will not be a concern, socomplexity shouldn't be wasted on optimizing it. I recommend posting to comp.lang.c++, as you'll get more insight than here.

EDIT: A writeup and minimal code example (properties.cpp). I do find the general concept interesting.
grinvader
ZSNES Shake Shake Prinny
Posts: 5626
Joined: Wed Jul 28, 2004 4:15 pm
Location: PAL50, dood !

Post by grinvader »

blargg wrote:1) different names, everyone understands, no subtle problems, and you don't give C programmers more ammo as to why they hate C++.

Not like I need more, it's already past critical mass.
皆黙って俺について来い!!

Code: Select all

<jmr> bsnes has the most accurate wiki page but it takes forever to load (or something)

Pantheon: Gideon Zhi | CaitSith2 | Nach | kode54
creaothceann
Seen it all
Posts: 2302
Joined: Mon Jan 03, 2005 5:04 pm
Location: Germany
Contact:

Post by creaothceann »

And that's why Delphi has properties. :P

Code: Select all

type


Foo = class
private
m_Value         : Integer;
public
property Value  : Integer  read m_Value;        // no function required
end;
vSNES | Delphi 10 BPLs
bsnes launcher with recent files list
byuu

Post by byuu »

If you have lots of information about the object, return a struct containing it:


I sort of do the same now, I just don't bother with making a copy of it (meaning it can be modified externally.)

I wouldn't say I use this info a lot, but we can get rid of any potential copy overhead anyway:

Code: Select all

class Cartridge {
public:
  struct info_t {
    Region region;
    MemoryMapper mapper;
    string name;
    bool sdd1;
    bool spc7110;
    bool dsp1;
    ...
  };
  const info_t& info() const { return cartinfo; }

protected:
  //property of Department of Redundancy Department
  info_t cartinfo;
};


EDIT: A writeup and minimal code example (properties.cpp). I do find the general concept interesting.


An interesting read. I see you've hit the same template friend class problem. Didn't mention the transience issue though.

I wanted to offer a custom callback mechanism to detect reads / writes to give it more functionality, but I'm reminded of your advice to only add stuff when you need it, heh.

May be difficult to nest two types, though:
property_t< callback_t<int> > foo;

callback_t::get,set will probably want full access to the property_t. Granting that with friendship would create a loophole to avoid the read-only access.

I suppose it'd be best to just make the property::set(property_t&, const T&) function virtual:
if(&p == &property_1) ...
else if(&p == &property_2) ...
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

but we can get rid of any potential copy overhead anyway: [snip code returning reference to contained info_t struct]

Yes, I didn't want to encourage premature optimization. But since it's been exposed, I'll add that this can even handle cases where some members are calculated only when info is called:

Code: Select all

class Cartridge { 
public:
    struct info_t {
        int unchanging;
        int calculated;
    };
    // since return info_ is inlined, compiler can access it just as efficiently
    // as if info_ were public and user were directly using it.
    const info_t& info() const { update_info(); return info_; }

private:
    int internal_info;
    mutable info_t info_;
   
    void update_info() const;
};

void Cartridge::update_info() const
{
    info_.calculated = info_.unchanging * 10 + internal_info;
    ...
}


I suppose it'd be best to just make the property::set(property_t&, const T&) function virtual:
if(&p == &property_1) ...
else if(&p == &property_2) ...

The functions the property calls in the parent class take the property as a reference. Each property has a different type, so the parent functions get overloaded. The parent can either ignore the property parameter, or can use static functions and get the parent reference from the passed property, thus avoiding the useless 'this' parameter' (good for efficiency maniacs):

Code: Select all

class Parent {
public:
    PROPERTY(value_cached,int,foo); // uses set( foo_t, int )
    PROPERTY(value_cached,int,bar); // uses set( bar_t, int )
   
private:
    int dependent;
   
    void set( foo_t&, int new_foo ); // could be made virtual
    static void set( bar_t& prop, int new_bar ); // avoids extra 'this' parameter
};

void Parent::set( foo_t&, int new_foo )
{
    dependent = (dependent & 0xFF00) | new_foo;
}

void Parent::set( bar_t& prop, int new_bar )
{
    prop.parent().dependent = (prop.parent().dependent & 0xFF) | (new_bar << 8);
}


creaothceann wrote:And that's why Delphi has properties.

The concept being discussed here goes beyond simple get/set properties, like the enter/exit one.
Locked