Proxy object / reference getters vs setters?

When I develop a general class, I often encounter a dilemma between the following design options:

template<class T> class ClassWithSetter { public: T x() const; // getter/accessor for x void set_x(const T& x); ... }; // vs template<class T> class ClassWithProxy { struct Proxy { Proxy(ClassWithProxy& c /*, (more args) */); Proxy& operator=(const T& x); // allow conversion from T operator T() const; // allow conversion to T // we disallow taking the address of the reference/proxy (see reasons below) T* operator&() = delete; T* operator&() const = delete; // more operators to delegate to T? private: ClassWithProxy& c_; }; public: T x() const; // getter Proxy x(); // this is a generalization of: T& x(); // no setter, since x() returns a reference through which x can be changed ... }; 

Notes:

  • the reason I return T instead of const T& in x() and operator T() is because the reference to x may not be available from the class if x is only stored implicitly (for example, suppose T = std::set<int> , but x_ type T stored as std::vector<int> )
  • Assume caching of proxy and / or x objects is not allowed

I am wondering what some scenarios will be in which one approach may be preferred over another, especially. in terms:

  • extensibility / generality
  • efficiency
  • developer efforts
  • custom effort

?

We can assume that the compiler is smart enough to use NRVO and fully embeds all methods.

Current personal observations:

(This part is not relevant for answering the question; it simply serves as a motivation and illustrates that sometimes one approach is better than another.)

One specific scenario in which the setter approach is problematic is as follows. Suppose you implement a container class with the following semantics:

  • MyContainer<T>& (mutable, read-write) - allows you to change both the container and its data implementation
  • MyContainer<const T>& (mutable, read-only) - allows you to modify the container, but not its data
  • const MyContainer<T> (immutable, read-write) - allows you to modify data, but not the container
  • const MyContainer<const T> (immutable, read-only) - does not change for container / data

where by “container modifications” I mean operations such as adding / removing elements. If I naively implement this using the setter approach:

 template<class T> class MyContainer { public: void set(const T& value, size_t index) const { // allow on const MyContainer& v_[index] = value; // ooops, // what if the container is read-only (ie, MyContainer<const T>)? } void add(const T& value); // disallow on const MyContainer& ... private: mutable std::vector<T> v_; }; 

The problem can be reduced by introducing a lot of template code that relies on SFINAE (for example, by getting a specialized template helper that implements both versions of set() ). However, the big problem is that it slows down the general interface, since we need either:

  • make sure calling set () in a read-only container is a compilation error
  • provide different semantics for the set () method for read-only containers.

On the other hand, although the proxy-based approach works neatly:

 template<class T> class MyContainer { typedef T& Proxy; public: Proxy get(const T& value, size_t index) const { // allow on const MyContainer& return v_[index]; // here we don't even need a const_cast, thanks to overloading } ... }; 

and the general interface and semantics are not violated.

One of the difficulties that I see with the proxy approach is Proxy::operator&() because there cannot be an object of type T that is stored / referenced to an accessible one (see notes above). For example, consider:

 T* ptr = &x(); 

which cannot be supported if x_ is not actually stored anywhere (either in the class itself or through methods (chains) called in member variables), for example:

 template<class T> T& ClassWithProxy::Proxy::operator&() { return &c_.get_ref_to_x(); } 

Does this mean that references to proxy objects are really superior when T& is available (i.e. x_ explicitly stored), since it allows:

  • batch / update delays (for example, imagine changes propagate from a proxy class destructor)
  • better caching control?

(In this case, the dilemma is between void set_x(const T& value) and T& x() .)

Edit: I changed typos in constness seters / accessors

+7
c ++ design proxy getter-setter proxy-classes
source share
3 answers

Like most designer dilemmas, I think it depends on the situation. In general, I would prefer the getters and seters template, since it is easier to code (there is no need for a proxy class for each field), it is easier to understand by another person (looking at your code) and more explicit under certain circumstances. However, there are situations where proxy classes can simplify the user’s work and hide implementation details. A few examples:

If your container is a kind of associative array, you can overload operator [] to get and set the value for a specific key. However, if the key has not been defined, you may need a special operation to add it. Here the proxy class is likely to be the most convenient solution, since it can handle = assignment differently as needed. However, this can be confusing for users: if this particular data structure has different times for adding vs settings, using a proxy makes it difficult to view, and using a set of set and put methods allows you to clear the separate time used by each operation.

What if the container does some kind of compression on T and keeps the compressed form? Although you can use a proxy server that performed compression / decompression if necessary, it would hide the costs associated with decompression compression from the user, and they could use it as if it were a simple assignment without heavy calculations. By creating getter / setter methods with the appropriate names, it can be made more obvious that they take significant computational effort.

Getters and seters also seem more extensible. Creating a getter and setter for a new field is easy, creating a proxy server that redirects operations for each property will be error prone to annoyance. What if later you need to expand your container class? With getters and setters, just make them virtual and override them in a subclass. For proxies, you may need to create a new proxy structure in each subclass. To avoid encapsulation breaking, you should probably get your proxy structure to use the superclass proxy structure to do some of the work, which can become quite confusing. With getters / setters, just call the supergetter / setter.

In general, recipients and setters are easier to program, understand, and modify, and they can make the costs of the operation visible. Therefore, in most situations, I would prefer them.

+1
source share

I think your ClassWithProxy interface mixes wrappers / proxies and containers. For containers, accessors such as

 T& x(); const T& x() const; 

as well as standard containers, for example, std::vector::at() . But usually access to members by reference is interrupted by encapsulation. For containers, this is convenience and part of the design.

But you noted that a reference to T not always available, so this will reduce the parameters of your ClassWithSetter interface, which should be a wrapper for T regarding how you store your type (while containers deal with how you store objects ) I would change the naming to make it clear that this might not be as efficient as a simple get / set.

 T load() const; void save(const T&); 

or something more in context. Now it should be obvious, changing T with a proxy breaks encapsulation again.

By the way, there is no reason not to use the shell inside the container.

0
source share

I think that perhaps part of the problem with your set implementation is that your idea of ​​how const MyContainer<T>& will behave is incompatible with the way standard containers behave and therefore are likely to be confused future code maintainers. The usual container type for "persistent container, mutable elements" is const MyContainer<T*>& , where you add an indirectness level to clearly indicate your intent to users.

This is how standard containers work, and if you use this mechanism, you do not need the container to be reinstalled, and the set function will be const .

All that I said prefers the set / get approach a bit, because if a particular attribute needs only get , you don't need to write set .

However, I prefer not to write direct access to members (for example, get / set or proxy), but instead provide a full named interface through which clients can access the functions of the class. In a trivial example, to show my meaning, instead of set_foo(1); set_bar(2); generate_report(); set_foo(1); set_bar(2); generate_report(); select a direct interface, for example generate_report(1, 2); , and avoid directly manipulating class attributes.

0
source share

All Articles