c ++定义类属性和类方法_如何向标准C ++类添加属性

c ++定义类属性和类方法

C ++属性 (C++ Properties)

One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property. These are like data members except they can have preconditions imposed on them prior to getting or setting their value. In C++ the general way to implement property like behaviour is to have a getter and/or setter member function. For the most part this suffices but there is an issue with this approach: you lose the syntax and semantics of a data member and, instead, have to deal with the syntax and semantics of a member function.

在许多其他的面向对象编程语言中可以找到的标准C ++缺少的功能之一就是属性 。 这些就像数据成员一样,只不过它们可以在先决条件 。 在C ++中,实现类似行为的属性的一般方法是具有getter和/或setter成员函数。 在大多数情况下,这是足够的,但是这种方法存在一个问题:您丢失了数据成员的语法和语义,而不得不处理成员函数的语法和语义。

What do we mean by this? Let's take a very simple example class called "account" that contains an int called "balance_".

这是什么意思? 让我们以一个非常简单的示例类(称为“帐户”)为例,该类包含一个名为“ balance_”的整数。

class account
{
public:
   int balance_;
};

As it currently stands "balance_" is a public data member. Although this gives us access to "balance_" it's uncontrolled - no preconditions can be imposed. This is bad OOP design. It means "account" has no control over the value of "balance_" and so cannot guarantee the value is sane. In other words we could set balance_ to any rogue value that may or may not be appropriate for what it represents. Let's make a change to ensure this is no longer the case.

按照目前的情况,“ balance_”是公共数据成员。 尽管这使我们可以访问“ balance_”,但它不受控制-无法施加任何先决条件。 这是糟糕的OOP设计。 这意味着“帐户”无法控制“ balance_”的值,因此不能保证该值是合理的。 换句话说,我们可以将balance_设置为可能适合或可能不适合其代表的任何恶意值。 让我们进行更改以确保不再是这种情况。

class account
{
private:
   int balance_;
};

That's it, now "balance_" is private so only "account" can change it. Of course, this isn't much use if we do want the outside world to change the value of "balance_". What we need is a way to get and set the value but in a way that "account" can ensure things are sane. Enter the getter and setter function.

就是这样,现在“ balance_”是私有的,因此只有“帐户”可以更改它。 当然,如果我们确实希望外界改变“ balance_”的值,这并没有太大用处。 我们需要的是一种获取和设置值的方式,但要确保“帐户”可以确保一切正常。 输入getter和setter函数。

class account
{
public:
   int get() const
   {
      return balance_;
   }
   
   void set(int balance)
   {
      balance_ = balance;
   }
private:
   int balance_;
};

Now we have functions getting and setting "balance_", which means we can put additional code in there to ensure, for example, that when we set "balance_" it cannot be negative (no one wants one of those!). Let's do just that.

现在,我们有了获取和设置“ balance_”的函数,这意味着我们可以在其中放置其他代码,以确保例如,当我们设置“ balance_”时,它不能为负(没人想要其中之一!)。 让我们开始吧。

class account
{
public:
   int get_val() const
   {
      return balance_;
   }
   
   void set_val(int balance)
   {
      if(balance < 0)         
      {
         // ERROR!
      }
         
      balance_ = balance;
   }
private:
   int balance_;
};

Great, finally a class that contains a member that we can get and set but in a controlled way. It's all good right? Well, yes and no. You see we are now stuck with using function syntax and semantics. This presents two issues:

太好了,最后一个包含一个成员的类,我们可以通过受控的方式来获取和设置该成员。 没事吧? 好吧,是的,不是。 您会看到我们现在仍然坚持使用函数语法和语义。 这带来了两个问题:

1. Syntax: When writing generic code we need to rely on a class implementing a get or set method; if it doesn't the code won't build.

1. 语法:

2. Semantics: You can't freely use get and set functions in an expression.

2. 语义:

Let's deal with syntax first. We are going to write a generic function that takes an object that models the pair concept and sets both their values (first and second) to a value.

让我们先处理语法。 我们将编写一个通用函数,该函数采用一个对象来对配对概念建模并将两个值(第一个和第二个)都设置为一个值。

template <typename pairT>
void func(pairT & mypair)
{
   mypair.first = 320;
   mypair.second = 240;
}

This will work very well with std::pair.

这将与std :: pair一起很好地工作。

std::pair<int, int> mypair;
func(mypair);

Let's say we wanted to implement our own pair object so we can implement some sanity checks. We could do this by aggregating std::pair.

假设我们要实现自己的对对象,以便实现一些健全性检查。 我们可以通过聚合 std :: pair来做到这一点。

class foo_pair
{
public:
   int get_first() const
   {
      return mypair_.first;
   }
   
   int get_second() const
   {
      return mypair_.second;
   }

   void set_first((int val)
   {
      if(val < 1 || val > 100)
         throw std::invalid_argument(
            "value must be between 1 and 100"
         );
         
      mypair_.first = val;
   }

   void set_second((int val)
   {
      if(val < 1 || val > 100)
         throw std::invalid_argument(
            "value must be between 1 and 100"
         );
         
      mypair_.first = val;
   }
private:
   std::pair<int, int> mypair_;
};

Now, let's try using this code with our function...

现在,让我们尝试将此代码与我们的函数一起使用...

foo_pair mypair;
func(mypair);

What's happened here? Simple. It won't compile because the function doesn't know it has to call set_first and set_second. Major fail! :(

这是怎么了 简单。 它不会编译,因为该函数不知道必须调用set_first和set_second。 大失败! :(

Now, let's take a look at the issue of Semantics. Consider this small code expression...

现在,让我们看一下语义学的问题。 考虑一下这个小代码表达式...

int x;
int y;
int z = x = y = 10;

If x and y were objects that represented special ints (a bounded int maybe) that implemented a set method. Would this still work? Of course not

如果x和y是表示实现设置方法的特殊整数(可能是有界整数)的对象。 这仍然有效吗? 当然不是

int x;
int y;
int z = x.set(10) = y.set(10); // this makes no sense 

Let see what we must do to make it work.

让我们看看我们必须做些什么才能使其工作。

foo x;
foo y;
x.set(10);
y.set(10);
int z = 10;

Notice how we are forced to use functions, which breaks the nice free semantics of using assignment operators? Even if we make set return the value it has just set this still doesn't work very well.

注意我们被迫使用函数,这打破了使用赋值运算符的自由语义吗? 即使我们使set返回它刚刚设置的值,它仍然不能很好地工作。

foo x;
foo y;
int z = x.set(y.set(10));

It's just ugly and non-intuitive.

这只是丑陋且非直觉的。

I hope that I've managed to demonstrate that although the getter and setter functions do serve a purpose they are a poor replacement for direct access to a variable. At this point I hope you are wondering, "great, how would a property help us"? Let's find out!

我希望我已经设法证明,尽管getter和setter函数确实达到了目的,但它们不能直接访问变量。 在这一点上,我希望您想知道,“太棒了,财产将如何帮助我们”? 让我们找出答案!

First, to give us a starting point, let's take a look at how a property could be defined using a language that has native support. We will use C# and re-implement the foo_pair class.

首先,为我们提供一个起点,让我们看一下如何使用具有本机支持的语言来定义属性。 我们将使用C#并重新实现foo_pair类。

class foo_pair
{
public:
   int first
   {
      get
      {
         return first_;
      }
      
      set
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }
            
         first_ = value;
      }
   }
   
   int second
   {
      get
      {
         return second_;
      }
      
      set
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }
            
         second_ = value;
      }
   }
   
private:
   int first_;
   int second_;
};

How neat is that? This implements a "first" and "second" property. Notice how each property has a get and set clause? You can implement either or both of these to make a property read/write, read-only or write-only. In this case we've implemented both so the property is read/write. Implementing only get makes it read-only and implementing only set makes it write only. As you can see the set clause can perform validation. Notice there is no function syntax so there is no actual value passed in? Instead C# provide access to a special implicit variable call "value", which will contain the actual value being set. The property allows for the sanity checks whilst preserving data member syntax and semantics.

那有多干净? 这实现了“第一”和“第二”属性。 注意每个属性如何具有get和set子句? 您可以实现这两者之一或全部以使属性为读/写,只读或只写。 在这种情况下,我们都实现了两者,因此该属性为读/写。 仅实现get使其变为只读状态,而仅实现set使其变为仅写入状态。 如您所见,set子句可以执行验证。 请注意,没有函数语法,因此没有传入任何实际值? 而是C#提供对特殊隐式变量调用“值”的访问,该隐式变量调用将包含要设置的实际值。 该属性允许进行完整性检查,同时保留数据成员的语法和语义。

So, the question is, can we do this in C++? The answer is sort of. The fact is there is no specific native support for properties in standard C++ (although some compilers to have vendor specific extensions) but the language does give us the tools we need to implement our own. Of course, we will never get the nice neat solution like we have in C# because properties are not part of the C++ language but we can get a reasonable facsimile. Let's look at the code that will allow us to implement properties in C++.

因此,问题是,我们可以在C ++中做到这一点吗? 答案是这样的。 事实是,标准C ++中没有对属性的特定本机支持(尽管某些编译器具有特定于供应商的扩展),但该语言确实为我们提供了实现自己的工具。 当然,我们永远不会得到像C#中那样好的解决方案,因为属性不是C ++语言的一部分,但是我们可以获得合理的传真。 让我们看一下允许我们在C ++中实现属性的代码。

#define property_(TYPE, OWNR, NAME, IMPL) \
   private: \
      class NAME ## __ \
      { \
      friend class OWNR; \
      public: \
         typedef NAME ## __ this_type; \
         typedef TYPE value_type; \
         NAME ## __ () {} \
         explicit NAME ## __ (value_type const & value) \
            : NAME ## (value) {} \
         IMPL \
      private: \
         value_type NAME ##; \
      }; \
   public: \
      NAME ## __ NAME;

#define get_ \
   operator value_type const & () const \
   { \
      return get(); \
   } \
   value_type const & get() const

#define set_ \
   this_type & operator = (value_type const & value) \
   { \
      set(value); \
      return *this; \
   } \
   void set(value_type const & value)

#define xprop_(NAME) \
   NAME ## . ## NAME

Pretty scary eh? That's ok - it's macros and they normally are! This article isn't going to teach you how to read or write macros but it's not that hard and a good tutorial will explain all you need to know to follow this code.

吓人吗? 没关系-它是宏,通常是! 本文不会教您如何读取或编写宏,但它并不难,一个好的教程将解释遵循此代码所需的全部知识。

So, how does this work? We then have four macros to facilitate adding a property class to a host class. All we do is use these macros to both define and implement a property.

那么,这是如何工作的呢? 然后,我们有四个宏,以方便将属性类添加到主机类。 我们要做的就是使用这些宏来定义和实现属性。

Let's take a look at what each macro does:

让我们看一下每个宏的作用:

property_ : this macro defines a property, which is really nothing more than a local class with the cast and assignment operators defined to call 'get' and 'set' respectively

property_ :此宏定义了一个属性,实际上只是一个本地类,其中定义了强制转换赋值运算符以分别调用“ get”和“ set”

This macro takes the following parameters:

该宏采用以下参数:

* TYPE - this is the type of the property (for example int)

* TYPE-这是属性的类型(例如int)

* OWNR - the host (parent) class name, required to be made a friend of the property allowing unprotected access

* OWNR-主机(父)类名称,必须成为该属性的朋友,以允许不受保护的访问

* NAME - this is the name of the property (for example "first" or "second" as per the example above)

* NAME-这是属性的名称(例如,根据上面的示例,例如“ first”或“ second”)

* IMPL - this is the code that defines get and/or set, use add either or both of the get_ and set_ macros here

* IMPL-这是定义get和/或set的代码,请在此处使用get_和set_宏之一或全部添加

get_   : this macro defines the non-const get clause - you need to add your own logic to specialise its behaviour between a { and a }

get_ :此宏定义了非常量get子句-您需要添加自己的逻辑以专门化{和}之间的行为

set_   : this macro defines the set clause - you need to add your own logic to specialise its behaviour between a { and a }

set_ :此宏定义set子句-您需要添加自己的逻辑以专门化{和}之间的行为

xprop_ : this macro allows the parent class to access the raw value unchecked variable bypassing the get and set methods

xprop_ :此宏允许父类绕过get和set方法访问原始值未经检查的变量

The astute reader may note that some operator semantics such as ++ and += will not work (my thanks to EE's Dan Rollins for pointing that out). These can be added after the get and set macros in the property definition, as required. When you add them make sure they call set to ensure proper value validation. We could trivialise this by defining additional macros for the operators we wish to support. I'll leave that as an exercise for the reader.

精明的读者可能会注意到,某些运算符语义(例如++和+ =)将不起作用(感谢EE的Dan Rollins指出了这一点)。 可以根据需要将它们添加到属性定义中的get和set宏之后。 添加它们时,请确保它们调用

The solution presented here tries to find a balance between simplicity and safety to provide property semantics - without proper language support there will always be a compromise we will need to make.

此处提出的解决方案试图在提供属性语义的简单性和安全性之间找到平衡-如果没有适当的语言支持,我们将始终需要做出妥协。

That's it - pretty simple really. Here is the canonical form for using these macros to add a property to a class.

就是这样-真的很简单。 这是使用这些宏向类添加属性的规范形式。

#include <property.hpp>

class foo_pair
{
public:
   foo_pair() :
      first(9), second(5) // initilising properties (optional, if not used they will default construct)
      {}

   // Implement get, set and ++ operators
   property_(

      int, foo_pair, first, // int foo_pair::first

      get_
      {
         return first;
      }

      set_
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }

         first = value;
      }

      this_type & operator ++() { set(first +1); return *this; }

      this_type operator ++(int)
      {
         this_type tmp(first);
         set(first +1);
         return tmp;
      }

   );

   // Implement get, set and += operator
   property_(

      int, foo_pair, second, // int foo_pair::second

      get_
      {
         return second;
      }

      set_
      {
         if(value < 1 || value > 100)         
         {
            // ERROR!
         }

         second = value;
      }

      this_type operator += ( int rhs )
      {
         set(second + rhs);
         return *this;
      }

   );

private:
   void bypass() // for internal use by foo_pair only!
   {
      // accessing property members, circumventing set and get
      xprop_(first) = 0;
      xprop_(second) = 100;
   }
};

template <typename pairT>
void func(pairT & mypair)
{
   mypair.first = 320;
   mypair.second = 240;
}

int main()
{
   foo_pair mypair;

   func(mypair); // Look, we can now call the generic function

   int z = mypair.first = mypair.second; // Look, we can now perform expressions using variable and not function syntax

   // Some additional operator manipulation
   mypair.first++;
   ++mypair.first;
   mypair.second+=10;
}

So, that's it. With a few simple macros we've managed to add properties to C++. Ok, in reality using some macro trickery we're just adding member classes with some specialised behaviour but in reality this is more or less what a property is anyway. These simple macros just simplify the process of adding the necessary boilerplate to the class to implement the property.

就是这样了。 通过一些简单的宏,我们设法将属性添加到C ++。 好的,实际上使用一些宏技巧,我们只是添加具有某些特殊行为的成员类,但实际上这或多或少是一个属性的本质。 这些简单的宏仅简化了将必要的样板添加到类以实现属性的过程。

Is there a cost of using this code? Not really. Everything is passed around as a reference and the size of the property class should be no larger than the variable it represents since it has no other members. As there is no dynamic polymorphism involved the compiler should be able to inline most (if not all) of this and, thus, optimise away the function calls involved. Of course, if the get or set are too complex this may not be the case but, then, this would be no different from implementing get or set function anyway.

使用此代码是否需要付费? 并不是的。 一切都作为参考传递,并且属性类的大小不应大于其表示的变量,因为它没有其他成员。 由于不涉及动态多态性,因此编译器应该能够内联大部分(如果不是全部),从而优化所涉及的函数调用。 当然,如果get或set太复杂,可能不是这样,但是无论如何,这与实现get或set函数没有什么不同。

Even if you decide using this code isn't for you, I do hope reading this article has, at least, given you an insight into another aspect of OOP development and design - one that is, sadly, missing from the current version (C++03) of C++.

即使您认为不适合使用此代码,我也希望阅读本文至少可以使您深入了解OOP开发和设计的另一个方面-可惜的是,当前版本缺少该方面(C ++ 03)的C ++。

翻译自: https://www.experts-exchange.com/articles/3843/How-to-add-properties-to-standard-C-classes.html

c ++定义类属性和类方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值