原文:https://www.codeproject.com/Articles/118921/C-Properties
Introduction
This article discusses how to implement C#-like properties in Visual C++ 6 or above. This article assumes that the reader has enough knowledge in the C++ language to understand the code (though the code snippet will be thoroughly explained). It is recommended that the reader understands macros, classes, functions, and typedefs; however, this code is available for anyone to use.
Background
If you are a new C++ or C# programmer, then properties may be a new concept. Properties are simply a convenient way of defining a getter and a setter for a given member variable. A getter is a member function that returns the literal value of the member variable that the property represents. The setter sets the literal value of the member variable that the property represents, but does not return a value. Properties in C# typically look like the following:
private int _x;
public int x
{
get
{
return _x;
}
set
{
_x = value;
}
}
Of course, get designates the getter and set designates the setter. Within the setter, a variable labeled value
exists that represents the rvalue of an assignment statement that involved your property. For example, in the following code snippet, the value
variable would contain the value 50
.
someObject.x = 50;
When the property is assigned a value, as in the above example, the setter member function is called and passed the rvalue as an argument to a parameter labeled value. Of course, the simplicity of properties allow us to not worry about the definition of these member functions and hides the actual calling of the function from us. We simply declare the getters and setters using the get
and set
keywords in C#.
Properties in C++
Unfortunately, C++ does not natively support properties. Properties in C++ involve writing out each function for each setter and getter. For example, the following code implements the same property as above, but in C++.
private:
int x;
public:
int getX()
{
return x;
}
void setX(int value)
{
x = value;
}
In order to use the setter and getter, we simply call the corresponding member function.
someObject.setX(50);
int x = someObject.getX();
The above code is perfectly fine and many people prefer these setters and getters as opposed to the C#-styled ones; however, there also exist many people who want C#-styled properties in C++. Because of this, Visual C++ 6.0 and above decided to implement a property declaration attribute.
__declspec( property (put=setFunction, get=getFunction) ) data-type property-name;
The above code allows us to tell Visual C++ that we want setFunction to be called every time property-name
is assigned to. Conversely, we want getFunction to be called every time we need to get the value of property-name
. Of course, we still have to define those member functions, but this brings us much closer to implementing C#-style properties.
(A small note to non-Visual C++ users: implementing these properties is possible; however, it requires a much higher level or work and this article does not address it.)
The Implementation
Of course our C++ implementation will not be the exact syntax as C# properties for obvious reasons, but nonetheless it is a close representation. Below is the only code you will need in order to implement properties in Visual C++.
#define PROPERTY(t,n) __declspec( property
( put = property__set_##n, get = property__get_##n ) ) t n;\
typedef t property__tmp_type_##n
#define READONLY_PROPERTY(t,n) __declspec( property (get = property__get_##n) ) t n;\
typedef t property__tmp_type_##n
#define WRITEONLY_PROPERTY(t,n) __declspec( property (put = property__set_##n) ) t n;\
typedef t property__tmp_type_##n
#define GET(n) property__tmp_type_##n property__get_##n()
#define SET(n) void property__set_##n(const property__tmp_type_##n& value)
At first sight, this code may look frightening, but fear not. The above code is extremely easy to use. To declare a property using these macros, we simply use one of the PROPERTY macros. Below is a sample implementation of the sample property used earlier.
private:
int _x;
public:
PROPERTY(int, x);
GET(x)
{
return _x;
}
SET(x)
{
_x = value;
}
And just as in C#, we can access our property like the following:
someObject.x = 50;
int x = someObject.x;
When we assign a value to the x
property, the rvalue (which is 50
in this example) is assigned to the variablevalue
, which is accessible in the setter. The code within the setter is then executed. When we get the value of the property, the code within our getter is executed and a value is returned (in this case, the value of _x
).
Read-Only, Write-Only, and Access Modifiers
The above code natively supports read-only, write-only, and access-modified properties. Each of the different types of properties have their own uses. For example, a read-only property should only be used if neither the parent class nor any object accessing the property does not need to write to the property. Conversely, write-only should only be used if neither the parent class nor any object accessing the property does not need to read from the property. In order to use read-only and write-only properties, we simply use the corresponding macro and only define the corresponding setter or getter. Below is an example of each.
// read-only
READONLY_PROPERTY(int, length);
GET(length)
{
return strlen(someString);
}
// write-only (rare)
WRITEONLY_PROPERTY(int, size);
SET(size)
{
_size = value;
}
Typically, neither read-only nor write-only properties are used. It is much more common to change the access-modifier of a property. Changing the access modifier of a property simply means to make it public
, private
,protected
, etc. It is common for read-only properties to be implemented by simply making the setter private
. However, the getter and setter do not need to be declared with the same access-modifier. For example, the gettercan be public
while the setter can be private
. Likewise, the getter can be private
and the setter can bepublic
.
Full Example
![screen2.png](https://i-blog.csdnimg.cn/blog_migrate/1da723132dd61ea62c06dbebc4ab2d57.png)
When learning any new concept, it helps to see a fully working example.
// properties.h
#ifndef _PROPERTIES_H
#define _PROPERTIES_H
#define PROPERTY(t,n) __declspec( property
( put = property__set_##n, get = property__get_##n ) ) t n;\
typedef t property__tmp_type_##n
#define READONLY_PROPERTY(t,n) __declspec( property (get = property__get_##n) ) t n;\
typedef t property__tmp_type_##n
#define WRITEONLY_PROPERTY(t,n) __declspec( property (put = property__set_##n) ) t n;\
typedef t property__tmp_type_##n
#define GET(n) property__tmp_type_##n property__get_##n()
#define SET(n) void property__set_##n(const property__tmp_type_##n& value)
#endif /* _PROPERTIES_H */
![](https://i-blog.csdnimg.cn/blog_migrate/0a9790198ad274bad3ea0c1649d06d71.png)
// main.cpp
#include <iostream>
#include <math.h>
#include "properties.h"
class Vector2
{
public:
float x;
float y;
READONLY_PROPERTY(float, Length);
GET(Length)
{
return sqrt((x*x + y*y));
}
};
int main()
{
Vector2 vec;
vec.x = 1;
vec.y = 1;
std::cout << "Length of vector(" << vec.x << ", " << vec.y << ") = ";
std::cout << vec.Length << "\n"; // <--- property, not a function call
return 0;
}
The above code works with any Visual C++ version above 6.0. It does not need any additional dependencies -- as the section title states, it is a full example.
How It Works
Now let us move on to the nitty-gritty. The way these properties actually work lie in the magic of C++ macros. Let us take our original C++ property example and see what our macros would actually generate.
private:
int _x;
public:
PROPERTY(int, x);
GET(x)
{
return _x;
}
SET(x)
{
_x = value;
}
The above code is not what the compiler actually sees after preprocessing is complete. We must replace ourPROPERTY
, GET
, and SET
with the expanded form of the macro. The expanded version would look like the following:
private:
int _x;
public:
__declspec(property(put = property__set_x, get = property__get_x)) int x;
typedef int property__tmp_type_x;
property__tmp_type_x property__get_x()
{
return _x;
}
void property__set_x(const property__tmp_type_x& value)
{
_x = value;
}
As you can see, our GET
and SET
macros expand to member functions, just like they do in C#. However, this is hidden from us when we use our macros so it appears more C#-like.
Conclusion
Many people seem to argue about whether it is better to use properties or simply use getter and setter member functions in C++. The simple fact is that both are using member functions to perform the getting and setting, and as such, neither have any runtime performance gain over the other -- they are exactly the same.