C#-esque properties in C++?

Started by
14 comments, last by phresnel 14 years, 9 months ago
Quote:Original post by Heptagonal
I'm probably better off sticking to foo.x(42) instead.
Most of the time I find that I don't need that many getters and setters. When I do though I use y = foo.x() for getters and foo.x(y) for setters.
Also: Immutable types FTW!
Advertisement
Quote:Original post by ibebrett
you are doing something wrong if your classes require getters and setters. mebbe one and a great while, but it should almost never come up.


And just in case it was missed, this applies to C# code as well. If you're just making public vanilla properties, you're probably doing it wrong.
I'd rather my properties have the signature:
template<typename T>class Property {private:  typedef boost::function1< void, T const& > setter;  setter set;  typedef boost::function0< T > getter;  getter get;public:  Property( setter set_, getter get_ );  operator T() { return get(); }  void swap( Property& other );  void swap( T& other );  Property& operator=( T const& src ) {    set(src);    return *this;  }  Property& operator+=( T const& src ) {    T tmp = get();    tmp += src;    return (*this)=tmp;  }  T operator+( T const& src ) const {    T tmp = *this;    tmp += src;    return tmp;  }  [...]};

with the 'stored value' property:
template<typename T>void StoredValueProperty: public Property<T>{  [...]};

that has a reference to the data, and marshalls functions that take a reference to T& and turns them into valid set/get functions for Property<T>, and:
template<typename T>void HasValueProperty: public StoredValueProperty<T>{private:  T t;  [...]};

which actually owns the T for you.

And yes, this won't be nearly perfectly efficient. But you are wanting to emulate a C# feature, so you really cannot expect it to be that efficient.

The Property code with all of those invalid overrides uses the feature of C++ that a template class's methods don't get validated unless called. So even if you have a string property for which += makes no sense, so long as nobody calls += your code is fine. Once they call +=, they get a compiler error at that point.

All of this is quite doable.

[Edited by - NotAYakk on July 6, 2009 2:44:32 PM]
Why not do something more like this:

#include <string>#include <iostream>template<typename T>class Property{public:  Property( )    : m_value( )  {  }  Property(const T& val)    : m_value(val)  {  }  operator T&( )  {    return m_value;  }  operator const T&( ) const  {    return m_value;  }  T operator->( ) const  {    return m_value;  }  template<typename U>  friend std::istream& operator>>(std::istream& is, Property<U>& p);    template<typename U>  friend std::ostream& operator<<(std::ostream& os, const Property<U>& p);private:  T m_value;};template<typename T, typename Context>class PropertyReadOnly{public:  operator T&( )  {    return m_value;  }  template<typename U, typename C>  friend std::ostream& operator<<(std::ostream& os, const PropertyReadOnly<U, C>& p);private:  friend Context;  T m_value;  PropertyReadOnly( )    : m_value( )  {  }  PropertyReadOnly(const T& val)    : m_value(val)  {  }  operator const T&( ) const  {    return m_value;  }};template<typename U>std::istream& operator>>(std::istream& is, Property<U>& p){  is >> p.m_value;  return is;}template<typename U>std::ostream& operator<<(std::ostream& os, const Property<U>& p){  os << p.m_value;  return os;}template<typename U, typename C>std::ostream& operator<<(std::ostream& os, const PropertyReadOnly<U, C>& p){  os << p.m_value;  return os;}class Foo{public:  Foo( )    : count(10), child(0)  {  }  Property<std::string> name;  PropertyReadOnly<int, Foo> count;  Property<Foo*> child;};int main( ){  Foo f;  f.name = std::string("Foo");  f.count = 1; // error  std::cout << f.name << std::endl;  std::cout << f.count << std::endl;  if (!f.child)  {    f.child = new Foo( );    f.child->name = std::string("Foochild");    delete f.child;    f.child = 0;  }  return 0;}


It's less than perfect, but quite a bit easier to set up and should have much less overhead. But like others have said, I personally don't generally feel the need for this type of property.
The goal is to have a member variable that 'acts like' a member variable of type T, but when read or written from, arbitrary code is run.

Ideally the arbitrary code will be syntactically easy to deal with.

C++ doesn't support this perfectly. It can get pretty close if you pretend your class has a pointer-to-data instead of instance-of-data in the syntactic sugar sense (ie, require *foo.bar += 7 instead of foo.bar += 7), as that gives you consistent operator-> behaviour.

Hmm.

What if the pattern we followed was that of an iterator, but we didn't have operator++ and the like.

There still is the problem of dealing with the copy constructor of the class that owns us. Ie:
struct A {  Property<int> foo:};int main(int, char const*const*) {  A a;  A b;  a = b;  a.foo = 6;}

Operator= on Property<int> has to behave very non-iterator/pointer like -- more like a reference.

Even then, you can imagine a Property with a 'back end' store ending up behaving very badly. This is an artefact of mixing reference (which the Property has) and pointer (which the 'back end' store might have) in the same class, and it causing very unexpected behaviour.
As said, you can get near, but not 100%.

I once tried hard, and this thread might be interesting.

This topic is closed to new replies.

Advertisement