4

My goal is to fire a event receiver when a value is changed in a class. My solution to this is using get and set functions. However, I do not want any performance problems when reading the values. In the class below:

class Vertex {
 public:
  const double& GetX() { return _x; } // should inline automatically
  const double& GetY() { return _y; } // ' '                     ' '
  void SetX(const double& x); // complex stuff in source file
  void SetY(const double& y); // ' '                      ' '
 private:
  double _x, _y;
}

I put the definition of the getters in the header files, because this should allow them to be inlined.

My question is: is there any way to automatically call a function when a value is changed that does not require me to include function definitions in the header file? Or is there perhaps a visibility level that allows anyone to read the data, but only the class itself to modify it? Or maybe something else?

I know an alternative approach would be manually calling an update function, but that looks ugly:

vertex.X = 5;
vertex.Update();

Thank you in advance.

Edit:

So far I've heard some great answers. The solution depends on your goal:

  • Easy: getters and setters; not very clean though.
  • Clean: property class (or proxy); implementing operators is not as easy though.

However, what about efficiency?

I just worked out a nice trick for the separate read/write-access permissions I speculated about earlier:

class Vertex {
 public:
  Vertex(double x, double y)
      : _x(x), _y(y), X(_x), Y(_y) { } // defined here for readability
  void SetX(double x) { _x = x; update(); } // defined here for readability
  void SetY(double y) { _y = y; update(); } // update would be the callback
  const double& X, Y;
 private:
  double _x, _y;
}
RPFeltz
  • 1,049
  • 2
  • 12
  • 21
  • 4
    Do you have profiler data indicating that doing this with getters and setters is too inefficient? If not, then I would strongly suggest just going with the getter/setter approach, since it's by far the easiest and cleanest way to do this. – templatetypedef Feb 04 '12 at 21:47
  • 1
    I agree with templatetypedef (except for the part about getters and setters being the cleanest way; I would say the cleanest way would be to have a proxy object with an `operator=` that called the event handler). – Seth Carnegie Feb 04 '12 at 21:49
  • @templatetypedef Actually, I don't use a profiler and am not quite sure what it is either. However, I know it indeed is rather efficient and easy, but I happened to wonder if there is an even cleaner way. – RPFeltz Feb 04 '12 at 21:50
  • @SethCarnegie That's an example of what I hoped to hear. Thank you, I'll try it right away. I also thought of trying to make a const reference to a private non-const value to make it const to outsiders and non-const to insiders. – RPFeltz Feb 04 '12 at 21:52

2 Answers2

11

If all you're looking for is clean, then probably the cleanest way to do this would be to have a "property" object, like in C#:

template<typename T>
class Property {
public:
    Property(std::function<void(T)> callback) : data(), callback(callback) { }

    Property& operator=(const T& newvalue) {
        data = newvalue;
        callback(data);

        return *this;
    }

    operator T() const {
        return data;
    }

private:
    T data;
    std::function<void(T)> callback;
};

class Vertex {
public:
    Vertex() : X(&xcallback), Y(&ycallback) { }

    Property<double> X, Y;
};

(The functions are inline for conciseness on SO, you can move them out if you want.) Then you can do

Vertex v;

v.X = 4;
v.Y = 2.3443;

And every time the value is assigned to, the respective callback will be called. And you can use v.X and v.Y in place of wherever you'd use a double as well so they behave like normal doubles (except for the fact that you'd have to implement -=, +=, etc).

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • Implementing all operators is a horrible amount of work; it is most certainly the cleanest though. – RPFeltz Feb 04 '12 at 22:11
  • 1
    Nice, but I think the function should be .. if the compiler can handle that .... – Yochai Timmer Feb 04 '12 at 22:11
  • @RPFeltz no, you only have to implement the assignment versions like `+=` and `-=`; but `+`, `-`, `*`, `/`, etc will all work without you having to define them because the `Property` will be converted to a `double` (or whatever) and then the operator will be applied to that – Seth Carnegie Feb 04 '12 at 22:12
  • @SethCarnegie Does it automatically do that? Awesome. Check my trick with permissions in my edited post out though. – RPFeltz Feb 04 '12 at 22:24
  • @RPFeltz yes it automatically does that. And that is a good trick too if you want to make it more clear that changing the variables does more than just updating their value (property objects can deceive clients into thinking that updating the variable is a cheap operation when in reality it can be arbitrarily expensive). – Seth Carnegie Feb 04 '12 at 22:25
  • @RPFeltz as for efficiency, they will typically be equally efficient (compilers are _very_ smart these days) – Seth Carnegie Feb 04 '12 at 22:27
  • @SethCarnegie I just noticed the references somehow make the class bigger. Perhaps this might not be a good idea after all, but the only alternative is using const_casts which may look out of place in encapsulation. – RPFeltz Feb 05 '12 at 00:46
  • @RPFeltz yes, references, like pointers, make the class bigger. You really shouldn't be concerned; 8 extra bytes for each object is less than negligible in all but extraordinary scenarios. IMO, most of your concerns about speed and memory are very very very premature optimisation. – Seth Carnegie Feb 05 '12 at 01:36
  • If you want to set the callback as a class method, add your function and change `X(&xcallback), Y(&ycallback)` for `X([this](double x) -> void { this->xcallback(x); }), Y([this](double y) -> void { this->ycallback(y); })` – ignacio Jul 17 '20 at 09:08
0

Don't optimize prematurely. Write code that is simple and easy to understand/modify, and only after you profile and see that indeed you have a bottleneck there, optimize.

I would go with the get/set approach, and call the event handler on the set method.

There is no access modifier that allows you to read data, but restrict access to only the class.

Also, you're not required to include the function definition in the header file. The cost of an extra jump instruction is 99% of the time negligeable.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • I suppose you're right. P.S. I worked out a nice trick for the access; Small example: `class A { public: A() : var(_var) {} void setVar(int val) { _var = val } const int& var; private: int _var; };` – RPFeltz Feb 04 '12 at 22:08
  • @RPFeltz why are you returning a reference to a member? – Luchian Grigore Feb 04 '12 at 22:10
  • @LuchianGrigore he has a member `const` reference to a member to avoid a function call – Seth Carnegie Feb 04 '12 at 22:11
  • @SethCarnegie I don't follow. Can you explain please? – Luchian Grigore Feb 04 '12 at 22:14
  • @LuchianGrigore in his small example, `const int& var` will refer to the member `int _var` so that one can use `v.var` instead of having to create and use something like `v.getVar()`. It has to be `const` because if it wasn't, you could modify `_var` without the callback being called. – Seth Carnegie Feb 04 '12 at 22:16
  • @SethCarnegie doesn't he still need to call the method since the member is private? – Luchian Grigore Feb 04 '12 at 22:20
  • @LuchianGrigore `_var` is private but `var` is public; the outside world can access `_var` through the reference `var` (but through which `_var` _cannot_ be changed), but only the object itself can access `_var` and change it. If the outside world wants to change the value, they have to go through `setVar`. – Seth Carnegie Feb 04 '12 at 22:22
  • @SethCarnegie I'm confused. Are you talking about your answer or the code in the question? – Luchian Grigore Feb 04 '12 at 22:29
  • @LuchianGrigore ha, neither, I was talking about the code he was talking about in his comment (and which he added to his question): `class A { public: A() : var(_var) {} void setVar(int val) { _var = val } const int& var; private: int _var; };` – Seth Carnegie Feb 04 '12 at 22:31