KhanS
KhanS

Reputation: 1195

Having Public properties in c++ class

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.

Upvotes: 16

Views: 14933

Answers (8)

Valter Jaakkola
Valter Jaakkola

Reputation: 1

Here’s a bit crude and simple implementation using a preprocessor macro to effortlessly generate nested classes that provide the functionality of getters and setters with the nice and clean syntax as if they were actual variables. No templates or function pointers are used (if that’s a plus), although your compiled program will have as many (sub)classes of the name property_* as there are PROPERTIES declarations, and like in Evan Teran’s solution, you need to give the property a reference to the owning class in the constructor.

More operators (operator++, operator+=) should be easy to add.

(I guess the reference to the owning class is ok, despite the circularity...? (x.property_age.x.property_age ...))

#include <iostream>
#include <stdexcept>

#define PROPERTY(type, name, owner, get_exprs, set_exprs)   \
    friend class property_ ##name;                          \
    class property_ ##name {                                \
        owner & x;                                          \
    public:                                                 \
        property_ ##name (owner & init): x(init) {}         \
        operator type () {                                  \
            get_exprs                                       \
        }                                                   \
        type operator= (const type value) {                 \
            set_exprs                                       \
            return value;                                   \
        }                                                   \
    } name ;

int current_year = 2020;

class person {
    int year_of_birth; // Integer is signed to demonstrate argument validation
public:
    // Remember to add each property and *this for them
    person(): year_of_birth(0), age(*this) {}

    const int& born() { return year_of_birth; }

    // Remember the semicolons
    PROPERTY(int, age, person,
    /*get:*/ return current_year - x.year_of_birth; ,
    /*set:*/ if (value < 0) throw std::invalid_argument("person::age : age cannot be negative");
             x.year_of_birth = current_year - value; )
};

int main() {
    person alice, bob;
    alice.age = bob.age = 28;
    alice.age = alice.age + 5;
    
    //alice.age = -7; //throws

    // Apparently the compiler does nice implicit conversion from
    // (the macro-generated class) 'property_age' to 'int'
    std::cout << "Alice: born: " << alice.born() << ", age: " << alice.age << '\n'
        << "Bob: born: " << bob.born() << ", age: " << bob.age << '\n'
        << "The mean of their ages is: " << (alice.age + bob.age) / 2.0 << '\n';
    
    return 0;
}

Upvotes: 0

Jon Purdy
Jon Purdy

Reputation: 55069

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.

Upvotes: 7

conio
conio

Reputation: 3718

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.

Upvotes: 12

Steve Guidi
Steve Guidi

Reputation: 20198

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.

Upvotes: 4

Thomas Matthews
Thomas Matthews

Reputation: 57749

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.

Upvotes: 1

Evan Teran
Evan Teran

Reputation: 90533

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

Upvotes: 3

SigTerm
SigTerm

Reputation: 26439

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.

Upvotes: 1

unwind
unwind

Reputation: 400079

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.

Upvotes: 1

Related Questions