View unanswered posts | View active topics It is currently Sat Apr 20, 2019 2:30 am



This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 
C++ permissions : creating read-only variables 
Author Message
Reply with quote
Post C++ permissions : creating read-only variables
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:
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:
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:
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:
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?


Thu Feb 19, 2009 6:33 pm
Reply with quote
Post 
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:
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;
}


Fri Feb 20, 2009 1:58 am
Regular
User avatar

Joined: Thu Jun 30, 2005 1:54 pm
Posts: 327
Location: USA
Reply with quote
Post 
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.


Fri Feb 20, 2009 2:43 am
Profile WWW
Reply with quote
Post 
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:
#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.


Fri Feb 20, 2009 3:39 am
Regular
User avatar

Joined: Thu Jun 30, 2005 1:54 pm
Posts: 327
Location: USA
Reply with quote
Post 
If you have lots of information about the object, return a struct containing it:
Code:
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.


Fri Feb 20, 2009 6:13 am
Profile WWW
ZSNES Shake Shake Prinny
User avatar

Joined: Wed Jul 28, 2004 4:15 pm
Posts: 5613
Location: PAL50, dood !
Reply with quote
Post 
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:
<jmr> bsnes has the most accurate wiki page but it takes forever to load (or something)

Pantheon: Gideon Zhi | CaitSith2 | Nach | kode54


Fri Feb 20, 2009 6:56 am
Profile
Seen it all
User avatar

Joined: Mon Jan 03, 2005 5:04 pm
Posts: 2302
Location: Germany
Reply with quote
Post 
And that's why Delphi has properties. :P

Code:
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


Fri Feb 20, 2009 8:37 am
Profile WWW
Reply with quote
Post 
Quote:
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:
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;
};


Quote:
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) ...


Fri Feb 20, 2009 7:10 pm
Regular
User avatar

Joined: Thu Jun 30, 2005 1:54 pm
Posts: 327
Location: USA
Reply with quote
Post 
Quote:
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:
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;
    ...
}


Quote:
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:
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.


Fri Feb 20, 2009 8:49 pm
Profile WWW
Display posts from previous:  Sort by  
This topic is locked, you cannot edit posts or make further replies.   [ 9 posts ] 

Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by ST Software.