tags:

views:

329

answers:

8

How do I have properties in C++ class, as you have in a C# class.

I don't want to have getter and setter methods.

+2  A: 

Unfortunately, you can't.

Andreas Bonini
Or as some might say, fortunately.
anon
@Neil: how so? It's just syntactic sugar. It can't possibly hurt to have them, they allow you to write cleaner and faster code. (faster as in you write it faster, not as in it runs faster)
Andreas Bonini
@Andreas, it also makes it less readable.
Michael Aaron Safyan
@Micheal: I disagree but I guess it's a subjective thing
Andreas Bonini
@Andreas, a better solution is something along the lines of Objetive-C's "synthesize" (where the getter/setter is automatically generated, but it is still there).
Michael Aaron Safyan
@Michael: Its been a *long* time since I used Objective-C, but doesn't `synthesize` simply hide the release/retain code for variables? Such naive properties are worthless, and in C++ really might as well just be public variables if that's all you can do with them. If I'm forgetting the ability to provide functional logic to the accessors, then nevermind.
Dennis Zickefoose
@andreas I've used properties extensively (I used to be a Delphi programmer) and they encourage a poor programming style where it is very easy to put an object into an invalid state. Of course, a good programmer won't write such code, but properties get misused a lot by bad programmers.
anon
Doesn't C++ have plenty of other features to allow bad programmers to shoot themselves? :P C++ doesn't need properties for that... C++ is designed for good programmers to be able to do the right thing, not to protect bad programmers from themselves, right?
AshleysBrain
`C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off`. http://www2.research.att.com/~bs/bs_faq.html
Andreas Bonini
@Neil: +1. I really dislike the fact that something that is really a function looks like a member variable.
Nemanja Trifunovic
@Nemanja: And I really dislike the fact that there's a language feature to make getters and setters easier. Member functions should make sense in terms of operations on the class not in terms of class implementation.
David Thornley
@David: In general I very much agree. However, there are some rare cases where getters/setters make sense. For instance, to change a color of a window at run time based on user input, you'd probably want an overloaded method called Color() in your Window class. One overload would be the "getter" and another one that takes i.e. a RGB color would be the "setter".But as I said, in majority of cases getters/setters and properties are just a result of poor design.
Nemanja Trifunovic
Since you can overload operator=, you CAN have properties.
Ben Voigt
@Nemanja: I'm not saying getters and setters are always bad, just mostly. I don't see a reason to make mostly undesirable things easier, as long as they're not difficult when needed. BTW, your Color() example is only incidentally a getter/setter. It's an operation that makes sense to the class: find or change the RGB value of a window. You wouldn't need to change those function declarations if you switched to an entirely different scheme to specify colors.
David Thornley
+1  A: 

You don't. C++ doesn't support properties like C# does. If you want code to run on set/get, it will have to be a method.

unwind
Properties really run methods in C# also. The compiler hides it from you.
Zan Lynx
+2  A: 

For behaviour that's kind of like this, I use a templated meta-accessor. Here's a highly simplified one for POD types:

template<class T>
struct accessor {

    explicit accessor(const T& data) : value(data) {}
    T operator()() const { return value; }
    T& operator()() { return value; }
    void operator()(const T& data) { value = data; }

private:

    accessor(const accessor&);
    accessor& operator=(const accessor&);
    T value;

};

Typical usage is like this:

struct point {
    point(int a = 0, int b = 0) : x(a), y(b) {}
    accessor<int> x;
    accessor<int> y;
};

point p;
p.x(10);
p.y(20);
p.x()++;
std::cout << p.x();

The compiler typically inlines these calls if you set things up right and have optimisation turned on. It's no more of a performance bottleneck than using actual getters and setters, no matter what optimisations happen. It is trivial to extend this to automatically support non-POD or enumerated types, or to allow callbacks to be registered for whenever data are read or written.

Edit: If you prefer not to use the parentheses, you can always define operator=() and an implicit cast operator. Here's a version that does just that, while also adding basic "stuff happened" callback support:

Further Edit: Okay, totally missed that someone already made a revised version of my code. Sigh.

Jon Purdy
Why are you using clunky functor style when you could have defined operator= ?
Ben Voigt
@Ben: Added differences for completeness. It slipped my mind how that crap looks in C#.
Jon Purdy
+1  A: 

Properties aren't supported in C++, but you can implement them:
1) By using templates
2) By making language extension and writing custom code preprocessor

Either approach won't be easy, but it can be done.

SigTerm
C++ supports overloading operator=, so no extensions are required.
Ben Voigt
@Ben Voigt This depends on what exactly you want to implement, and how many properties you want. With large amount of code, introducing a keyword or two and writing code preprocessor will be better idea - it will make code more readable.
SigTerm
+5  A: 

You can use a solution similar to that Jon suggested, yet retaining ordinary C++ semantics using operator overloading. I've slightly modified Jon's code as following (explanations follow the code):

#include <iostream>

template<typename T>
class Accessor {
public:
    explicit Accessor(const T& data) : value(data) {}

    Accessor& operator=(const T& data) { value = data; return *this; }
    Accessor& operator=(const Accessor& other) { this->value = other.value; return *this; }
    operator T() const { return value; }
    operator T&() { return value; }

private:
    Accessor(const Accessor&);


    T value;

};

struct Point {
    Point(int a = 0, int b = 0) : x(a), y(b) {}
    Accessor<int> x;
    Accessor<int> y;
};

int main() {
    Point p;
    p.x = 10;
    p.y = 20;
    p.x++;
    std::cout << p.x << "," << p.y << std::endl;

    p.x = p.y = 15;
    std::cout << p.x << "," << p.y << std::endl;

    return 0;
}

We overload operator= to retain the usual assignment syntax instead of a function-call-like syntax. We use the cast operator as a "getter". We need the second version of the operator= to allow assignment of the second kind in main().

Now you can add to Accessor's constructor function pointers, or better - functors - to call as getters/setters in any way seems right to you. The following example assumes the setter function return bool to convey agreement to setting the new value, and the getter can just modify it on it's way out:

#include <iostream>
#include <functional>
#include <cmath>

template<typename T>
class MySetter {
public:
    bool operator()(const T& data)
    {
        return (data <= 20 ? true : false);
    }
};

template<typename T>
class MyGetter {
public:
    T operator()(const T& data)
    {
        return round(data, 2);
    }

private:
    double cint(double x) {
        double dummy;
        if (modf(x,&dummy) >= 0.5) {
            return (x >= 0 ? ceil(x) : floor(x));
        } else {
            return (x < 0 ? ceil(x) : floor(x));
        }
    }

    double round(double r, int places) {
        double off = pow(10.0L, places);
        return cint(r*off)/off;
    }
};

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>>
class Accessor {
public:
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {}

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; }
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; }
    operator T() const { value = getter(value); return value;}
    operator T&() { value = getter(value); return value; }

private:
    Accessor(const Accessor&);

    T value;

    G getter;
    S setter;

};

struct Point {
    Point(double a = 0, double b = 0) : x(a), y(b) {}
    Accessor<double> x;
    Accessor<double> y;
};

int main() {
    Point p;
    p.x = 10.712;
    p.y = 20.3456;
    p.x+=1;
    std::cout << p.x << "," << p.y << std::endl;

    p.x = p.y = 15.6426;
    std::cout << p.x << "," << p.y << std::endl;

    p.x = p.y = 25.85426;
    std::cout << p.x << "," << p.y << std::endl;

    p.x = p.y = 19.8425;
    p.y+=1;
    std::cout << p.x << "," << p.y << std::endl;

    return 0;
}

However, as the last line demonstrates it has a bug. The cast operator returning a T& allows users to bypass the setter, since it gives them access to the private value. One way to solve this bug is to implement all the operators you want your Accessor to provide. For example, in the following code I used the += operator, and since I removed the cast operator returning reference I had to implement a operator+=:

#include <iostream>
#include <functional>
#include <cmath>

template<typename T>
class MySetter {
public:
    bool operator()(const T& data) const {
        return (data <= 20 ? true : false);
    }
};

template<typename T>
class MyGetter {
public:
    T operator() (const T& data) const {
        return round(data, 2);
    }

private:
    double cint(double x) const {
        double dummy;
        if (modf(x,&dummy) >= 0.5) {
            return (x >= 0 ? ceil(x) : floor(x));
        } else {
            return (x < 0 ? ceil(x) : floor(x));
        }
    }

    double round(double r, int places) const {
        double off = pow(10.0L, places);
        return cint(r*off)/off;
    }
};

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>>
class Accessor {
private:
public:
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {}

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; }
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; }
    operator T() const { return getter(value);}

    Accessor& operator+=(const T& data) { if (setter(value+data)) value += data; return *this; }

private:
    Accessor(const Accessor&);

    T value;

    G getter;
    S setter;

};

struct Point {
    Point(double a = 0, double b = 0) : x(a), y(b) {}
    Accessor<double> x;
    Accessor<double> y;
};

int main() {
    Point p;
    p.x = 10.712;
    p.y = 20.3456;
    p.x+=1;
    std::cout << p.x << "," << p.y << std::endl;

    p.x = p.y = 15.6426;
    std::cout << p.x << "," << p.y << std::endl;

    p.x = p.y = 25.85426;
    std::cout << p.x << "," << p.y << std::endl;

    p.x = p.y = 19.8425;
    p.y+=1;
    std::cout << p.x << "," << p.y << std::endl;

    return 0;
}

You'll have to implements all the operators you're going to use.

conio
+1 for beating me to the punch, but I don't like the idea of providing tons of operator overloads like that. Another way to take care of possibly externally-invalidated values is to offer a different "was written" callback that is invoked when the value is next read.
Jon Purdy
Considering that you only define all those operators once in the template class I don't think it's that terrible. STL or boost code is horrific compares to this (and generally speaking too:)) in my opinion.But there are other options too. One must consider one's circumstances and make one's choice accordingly. :)
conio
+2  A: 

Here's a PoC implementation I did a while back, works nicely except that you need to set something up in the constructor for it to work nice and smoothly.

http://www.codef00.com/code/Property.h

Here's the example usage:

#include <iostream>
#include "Property.h"


class TestClass {
public:
    // make sure to initialize the properties with pointers to the object
    // which owns the property
    TestClass() : m_Prop1(0), m_Prop3(0.5), prop1(this), prop2(this), prop3(this) {
    }

private:
    int getProp1() const {
        return m_Prop1;
    }

    void setProp1(int value) {
        m_Prop1 = value;
    }

    int getProp2() const {
        return 1234;
    }

    void setProp3(double value) {
        m_Prop3 = value;
    }

    int m_Prop1;
    double m_Prop3;

public:
    PropertyRW<int, TestClass, &TestClass::getProp1, &TestClass::setProp1> prop1;
    PropertyRO<int, TestClass, &TestClass::getProp2> prop2;
    PropertyWO<double, TestClass, &TestClass::setProp3> prop3;
};

and some usage of this class...

int main() {
    unsigned int a;
    TestClass t;
    t.prop1 = 10;
    a = t.prop1;
    t.prop3 = 5;
    a = t.prop2;
    std::cout << a << std::endl;
    return 0;
}

There are two annoyances with this approach:

  1. You need to give the property a pointer to its owning class.
  2. The syntax to declare a property is a bit verbose, but I bet I can clean that up a bit with some macros
Evan Teran
A little awkward, yes, but the idea of registering the accessor with `this` is a good one, since it easily allows properties to play tricks with who can access them based on instance and type information.
Jon Purdy
+1  A: 

You could provide get and set methods that have similar names to the data members:

class Example
{
  private:
     unsigned int x_;
     double d_;
     std::string s_s;
  public:
     unsigned int x(void) const
     { return x_;}

     void x(unsigned int new_value)
     { x_ = new_value;}

     double d(void) const
     { return d_;}
     void d(double new_value)
     { d_ = new_value;}

     const std::string& s(void) const
     { return s_;}
     void s(const std::string& new_value)
     { s_ = new_value;}
};

Although this comes close, as it requires using '()' for each member, it doesn't meet the exact functionality of properties that Microsoft Languages provide.

The closest match for properties is to declare the data members as public.

Thomas Matthews
Wrong, by overloading operator= you can get the exact syntax (for the user) and capabilities of any language that provides properties for you.
Ben Voigt
+2  A: 

If you don't care that your C++ code won't compile with anything other than the Microsoft Visual C++ compiler, then you can use some of the compiler's non-standard extensions.

For instance, the following code will create a C#-like property called MyProperty.

struct MyType
{
    // This function pair may be private (for clean encapsulation)
    int get_number() const { return m_number; }
    void set_number(int number) { m_number = number; }

    __declspec(property(get=get_number, put=set_number)) int MyProperty;
private:
    int m_number:
}

int main()
{
    MyType m;
    m.MyProperty = 100;
    return m.MyProperty;
}

More information on this Microsoft-specific language extension is available here.

Steve Guidi