简体   繁体   English

用于自动 getter-setter 方法的 C++ 类模板 - 好/坏的做法?

[英]C++ class template for automatic getter-setter methods - good/bad practice?

Is it good practice to use template class objects with implicit getters and setters for attributes in (nearly) POD classes?在(几乎)POD 类中使用带有隐式 getter 和 setter 的模板类对象是一种好习惯吗?

Consider the following template example:考虑以下模板示例:

template<typename T>
class Attribute
{
protected:
    T m_val;
public:
    T * getAddress() { return &m_val; }
    T get() { return m_val; }
    void set(T v) { m_val = v; }
};

and its usage:及其用法:

class A
{
public:
    Attribute<float> floatAttr;
    Attribute<int> intAttr;
    Attribute<long> longAttr;
};

With this it is possible to have encapsulated data but with less implementation overhead.这样就可以封装数据,但实现开销更少。

Is this bad or good practice (and why)?这是不好的做法还是好的做法(为什么)?

Edit: To state the advantages i see in this.编辑:陈述我在此看到的优势。 There is no need to implement every getter setter function by hand, but there are still the usual advantages of these functions:无需手动实现每个 getter setter 函数,但这些函数仍然具有通常的优点:

  • The data is encapsulated and the client needs to use the getter setter functions, which still can be implemented in a different way later on.数据被封装了,客户端需要使用getter setter函数,后面仍然可以用不同的方式实现。
  • The internal usage is still hidden and can be changed.内部使用仍然是隐藏的,可以更改。
  • Getter and Setter functions can be passed around as lambda functions. Getter 和 Setter 函数可以作为 lambda 函数传递。

In some other other languages, getters and setters are a way of preventing implementation detail escape into the interface;在其他一些语言中,getter 和 setter 是一种防止实现细节逃逸到接口中的方法。 once you expose a field directly, you may not be able to later re-implement as a property (with getter and setter functions) without changing all sites in the code which access the field.一旦您直接公开一个字段,您以后可能无法在不更改访问该字段的代码中的所有站点的情况下将其重新实现为属性(使用 getter 和 setter 函数)。

In C++, this doesn't (so strongly) apply.在 C++ 中,这并不(如此强烈地)适用。 You can change the type of any field to a class which overrides operator= , and which converts to the required type implicitly (for the "get" side).您可以将任何字段的类型更改为覆盖operator=的类,并隐式转换为所需的类型(对于“get”端)。 (There are of course some uses where this doesn't apply; if a pointer or reference to the field is created in client code, for example - although I would personally avoid doing that and consider it dubious practice). (当然有一些不适用的用途;例如,如果在客户端代码中创建了对该字段的指针或引用 - 尽管我个人会避免这样做并认为这是可疑的做法)。

Also, because C++ is statically typed, it is also easier for tools (IDEs etc) to provide automatic refactoring, if you ever need to change a field and corresponding accesses into a getter/setter pair with appropriate calls.此外,由于 C++ 是静态类型的,因此工具(IDE 等)也更容易提供自动重构,如果您需要通过适当的调用将字段和相应的访问更改为 getter/setter 对。

In point of evidence, here is an alteration of your Attribute template which allows your "attribute" to act as if it were a directly-exposed field (except that & will return the address of the attribute rather than the hidden field):就证据而言,这是您的Attribute模板的更改,它允许您的“属性”充当直接公开的字段(除了&将返回属性的地址而不是隐藏字段):

template<typename T>
class Attribute
{
protected:
    T m_val;
public:
    operator T() { return m_val; }
    T &operator=(const T &a) { m_val = a; return m_val; }
};

If you really wanted to, you could also override operator& :如果你真的想要,你也可以覆盖operator&

T *operator&() { return &m_val; }

... however doing so largely breaks encapsulation (and for that matter, you might consider changing the return type of operator= to T or void for the same reason). ...但是这样做在很大程度上破坏了封装(就此而言,出于同样的原因,您可能会考虑将operator=的返回类型更改为Tvoid )。

If you had originally exposed the field directly, you could replace its definition with an instance of the above template, and most uses would be unaffected.如果您最初直接公开了该字段,则可以将其定义替换为上述模板的实例,并且大多数用途都不会受到影响。 This shows one reason why the getter/setter pattern is not always necessary in C++.这说明了为什么在 C++ 中并不总是需要 getter/setter 模式的一个原因。

Your own solution, while it encapsulates the original field behind getter/setter functions, actually also exposes another field: the Attribute<T> member ( floatAttr etc in your example).您自己的解决方案虽然封装了 getter/setter 函数后面的原始字段,但实际上还公开了另一个字段: Attribute<T>成员(在您的示例中为floatAttr等)。 For this to work as a means of encapsulation, you rely on users not knowing (or caring) about the type of the attribute field itself;为了使其作为一种封装方式起作用,您依赖于不知道(或关心)属性字段本身类型的用户; that is, you expect that no-one does:也就是说,您希望没有人这样做:

A a;
Attribute<float> & float_attr = a.floatAttr;

Of course, if they do not do this and access the fields in the manner you intended, then it would indeed be possible to later change the implementation by altering the type of the "attribute" field:当然,如果他们不这样做并以您想要的方式访问字段,那么以后确实可以通过更改“属性”字段的类型来更改实现:

A a;
float f = a.floatAttr.get();

... so in this sense, you achieve some encapsulation; ...所以从这个意义上说,你实现了一些封装; the real issue is that there is a better way to do it.真正的问题是有更好的方法来做到这一点。 :) :)

Finally, it bears mentioning that both your proposed Attribute template as well as the alternative I show above both move the field into a class ( Attribute<T> for some T) that is separate from the original parent class ( A ).最后,值得一提的是,您提出的Attribute模板以及我在上面显示的替代方案都将字段移动到与原始父类 ( A ) 分开的类中 ( Attribute<T>对于某些 T )。 If the implementation was to be changed, it is now limited to some degree by this fact;如果要更改实现,现在在某种程度上受到这一事实的限制; the attribute object does not naturally have a reference to the object that contains it.属性对象自然不会引用包含它的对象。 As an example, suppose I have a class B which has an attribute level :例如,假设我有一个B类,它有一个属性level

class B {
    public:
    Attribute<int> level;
};

Now suppose I later add a "miminum level" field, min_level :现在假设我稍后添加一个“最低级别”字段min_level

class B {
    public:
    Attribute<int> level;
    Attribute<int> min_level;
};

Further, suppose I now want to constrain level , on assignment, to the value of min_level .此外,假设我现在想在分配时将level限制为min_level的值。 This will not be straightforward!这不会是直截了当的! Although I can give level a new type with a custom implementation, it will not be able to access the min_level value from the containing object:尽管我可以通过自定义实现为level提供一个新类型,但它将无法从包含对象访问min_level值:

class LevelAttribute {
    int m_val;
    public:
    T &operator=(const T &a) {
        m_val = std::max(min_level, a); // error - min_level not defined
    }
}

To get it to work, you'd need to pass the containing object into the LevelAttribute object, which means storing an extra pointer.为了让它工作,您需要将包含对象传递给LevelAttribute对象,这意味着存储一个额外的指针。 The typical, old-fashioned setter, which is declared as a function directly in the class that holds the field, avoids this problem.典型的老式 setter 直接在持有该字段的类中声明为函数,避免了这个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM