本机 C++ 中的类 C# 属性?
Posted
技术标签:
【中文标题】本机 C++ 中的类 C# 属性?【英文标题】:C#-like properties in native C++? 【发布时间】:2010-11-19 12:44:00 【问题描述】:在C# / .NET 你可以这样做:
someThing.text = "blah";
String blah = someThing.text;
但是,上面的代码实际上并没有直接与 someThing 的文本字符串交互,它使用了一个 get 和 set 属性。同样,也可以使用只读属性。
有没有办法在原生 C++ 中做类似的事情? (不是 C++ .NET)
【问题讨论】:
我不是专家,但如果你想把someThing.text
当作一个公众成员,为什么不让它成为一个公众成员呢?
通过 getter 和 setter 暴露成员(即使是只读的)是不好的 OO。您正在将对象的内部表示暴露给世界。即使这通过使用方法(隐藏在属性的语法糖后面)得到了轻微的保护,它也提供了一个必须维护的公共 API。问题是你为什么要暴露你的成员?对象应该使用内部表示来执行任务,而不是将其暴露给其他人执行任务。而不是公开实现,而是公开使用表示的操作方法。
我质疑为掩盖代码而付出效率成本的效用。
Does C++11 have C#-style properties?的可能重复
【参考方案1】:
警告:这是一个半开玩笑的回应,太可怕了!!!
是的,这是可能的:)
template<typename T>
class Property
private:
T& _value;
public:
Property(T& value) : _value(value)
// eo ctor
Property<T>& operator = (const T& val)
_value = val;
return *this;
; // eo operator =
operator const T&() const
return _value;
; // eo operator ()
;
然后声明你的类,为你的成员声明属性:
class Test
private:
std::string _label;
int _width;
public:
Test() : Label(_label)
, Width(_width)
;
Property<std::string> Label;
Property<int> Width;
;
并调用 C# 风格!
Test a;
a.Label = "blah";
a.Width = 5;
std::string label = a.Label;
int width = a.Width;
【讨论】:
我过去尝试过这个,虽然我现在倾向于避免它,因为阅读代码的人没有意识到它不仅仅是一个公共字段,因为它在 c++ 中并不常见......但我是惊讶地发现编译器通常足够好,可以使其与直接对现场进行评估一样有效。 @jcoder 我不明白。它基本上变成了m_Test
public。为什么不首先将m_Test
公开并避免任何额外的编码?
我不知道你为什么会用“可怕”来开头。诚然,它不像 C# 中的属性那么灵活,但是当涉及到语法糖果因素(实际上所有属性都是)时,我认为它很棒。一个爽朗且赚得盆满钵满的 +1
这当然需要一种干净的方法来分别为每个属性指定 getter 和 setter..
感谢 Moo-Juice,我能够通过使用断点来利用此代码进行跟踪,以找出成员变量是如何设置为一个从未被设置为的值的。有问题的调用站点有一个错误。【参考方案2】:
在 .NET 中,属性是在幕后发出的真正 get
和 set
函数的语法糖(实际上它们不仅仅是语法糖,因为属性在生成的 IL 中发出并且可以与反射一起使用)。因此,在 C++ 中,您需要显式编写这些函数,因为没有属性这样的概念。
【讨论】:
但是它如何知道用户是想获取还是设置呢?是否需要使用运算符重载? 当它位于赋值运算符 (=
) 的左侧时,您正在设置,当它位于右侧时,您正在获取并且编译器足够聪明,可以解决这个问题。
在其上编写两个函数将返回一个引用,一个将返回一个 const 引用非常有用,编译器将确定何时调用哪个。但这将允许在任何地方(在 const 函数内)进行这些调用。
-1 正如Moo-Juice's answer 所证明的那样,“在 C++ 中,您需要显式编写这些函数,因为没有属性这样的概念”是微不足道的,直接错误的。 C++ 中的属性还有其他方法,包括源代码预处理。然而,C++ 中可移植属性的技术可能性并不意味着这样做是个好主意... :-)【参考方案3】:
我警告你,它不完全兼容原生 C++:仅限 Microsoft 特定的 C++。
微软编译器允许你使用declspec(property)
,这样:
struct S
int i;
void putprop(int j)
i = j;
int getprop()
return i;
// here you define the property and the functions to call for it
__declspec(property(get = getprop, put = putprop)) int the_prop;
;
int main()
S s;
s.the_prop = 5; // THERE YOU GO
return s.the_prop;
更多详细信息请参见 Microsoft 文档:declspec(property)。
【讨论】:
不过,这对于那些使用 MSVC 进行非 X 平台项目的人来说非常棒!很好的发现:)【参考方案4】:Moo-Juice 的答案看起来很酷,但有一个缺点:您不能像在 C# 中那样使用 T
类型的普通表达式那样的这些属性。
例如,
a.text.c_str()
不会编译 (‘class Property<std::basic_string<char> >’ has no member named ‘c_str’
)
std::cout << a.text
也不会编译 (template argument deduction/substitution failed
)
我建议对template<typename T> class Property
进行以下改进:
T& operator() ()
return _value;
T const& operator() () const
return _value;
然后就可以用()
访问属性的成员了,比如:
char const *p = a.text().c_str();
您可以在必须推导类型的表达式中使用该属性:
std::cout << a.text();
【讨论】:
【参考方案5】:.NET 中的属性与get
和/或set
成员函数相关联,因此它实际上只是语法糖。最接近 C++ 的方法是使用重载为 getter 和 setter 赋予相同的名称:
const std::string &test() const return text_;
void test(const std::string &value) text_ = value;
显然,您仍然需要为调用提供括号:
someThing.text("blah");
String blah = someThing.text();
【讨论】:
【参考方案6】:是的,但它是特定于供应商的。微软有 declspec(property)。 C++Builder 的实现更高级(通过供应商特定的 __property 关键字),因为您可以拥有索引访问器(可以是您希望的任何类型)。
也检查一下(不依赖供应商特定的关键字):http://www.codeproject.com/KB/cpp/cpp_property_indexer.aspx
【讨论】:
【参考方案7】:#include <iostream>
#include <string>
using namespace std;
// ------------------------------------------------------------------
#define PROPERTY_GET_SET(CLASS, NAME, TYPE) GetSetProperty<CLASS, TYPE> NAME() return GetSetProperty<CLASS, TYPE>(this, &CLASS::get_##NAME, &CLASS::set_##NAME);
#define PROPERTY_GET(CLASS, NAME, TYPE) GetProperty<CLASS, TYPE> NAME() return GetProperty<CLASS, TYPE>(this, &CLASS::get_##NAME);
#define PROPERTY_SET(CLASS, NAME, TYPE) SetProperty<CLASS, TYPE> NAME() return SetProperty<CLASS, TYPE>(this, &CLASS::set_##NAME);
template <typename CLASS, typename TYPE>
struct GetSetProperty
typedef TYPE (CLASS::*Getter_t)() const;
typedef void (CLASS::*Setter_t)(TYPE);
GetSetProperty(CLASS* instance, Getter_t getter, Setter_t setter) : m_instance(instance), m_getter(getter), m_setter(setter)
operator TYPE() const return (this->m_instance->*this->m_getter)();
GetSetProperty<CLASS, TYPE>& operator=(TYPE value) (this->m_instance->*this->m_setter)(value); return *this;
CLASS* const m_instance;
const Getter_t m_getter;
const Setter_t m_setter;
;
template <typename CLASS, typename TYPE>
struct GetProperty
typedef TYPE (CLASS::*Getter_t)() const;
GetProperty(CLASS* instance, Getter_t getter) : m_instance(instance), m_getter(getter)
operator TYPE() const return (this->m_instance->*this->m_getter)();
CLASS* const m_instance;
const Getter_t m_getter;
;
template <typename CLASS, typename TYPE>
struct SetProperty
typedef void (CLASS::*Setter_t)(TYPE);
SetProperty(CLASS* instance, Setter_t setter) : m_instance(instance), m_setter(setter)
SetProperty<CLASS, TYPE>& operator=(TYPE value) (this->m_instance->*this->m_setter)(value); return *this;
CLASS* const m_instance;
const Setter_t m_setter;
;
template <typename CLASS, typename TYPE>
ostream& operator<<(ostream& ostr, const GetSetProperty<CLASS, TYPE>& p) ostr << (p.m_instance->*p.m_getter)(); return ostr;
template <typename CLASS, typename TYPE>
ostream& operator<<(ostream& ostr, const GetProperty<CLASS, TYPE>& p) ostr << (p.m_instance->*p.m_getter)(); return ostr;
// ------------------------------------------------------------------
class Dummy
public:
Dummy() : m_value1(42)
PROPERTY_GET_SET(Dummy, Value1, int);
PROPERTY_GET_SET(Dummy, Value2, const string&);
protected:
virtual int get_Value1() const return this->m_value1;
virtual void set_Value1(int value) this->m_value1 = value;
virtual const string& get_Value2() const return this->m_value2;
virtual void set_Value2(const string& value) this->m_value2 = value;
private:
int m_value1;
string m_value2;
;
int main(int argc, char* argv[])
Dummy d;
cout << d.Value1() << endl;
d.Value1() = 3;
cout << d.Value1() << endl;
cout << d.Value2() << endl;
d.Value2() = "test";
cout << d.Value2() << endl;
return 0;
// ------------------------------------------------------------------
【讨论】:
【参考方案8】:通过使用 std::function 你可以非常接近。功能方面,一切都在这里。
首先创建模板属性类:
#include <functional>
template<class T>
class Property
std::function<T (void)> _get;
std::function<void(const T&)> _set;
public:
Property(
std::function<T (void)> get,
std::function<void(const T&)> set)
: _get(get),
_set(set)
Property(
std::function<T(void)> get)
: _get(get),
_set([](const unsigned int&))
operator T () const return _get();
void operator = (const T& t) _set(t);
;
通过创建类似于 C# 中的 get 和 set 方法在类中使用属性:
class Test
private:
std::string _label;
public:
Property<std::string> Label = Property<std::string>
(
[this]()->std::string
return this->_label;
,
[this](const std::string& value)
this->_label = value;
);
Property<unsigned int> LabelSize = Property<unsigned int>
(
[this]()->unsigned int
return this->_label.size();
);
;
测试此代码:
Test test;
test.Label = "std functional";
std::cout << "label = " << std::string(test.Label) << std::endl
<< "label size = " << int(test.LabelSize) << std::endl;
会输出
label = std functional
label size = 14
我认为这是你可以在 c++ 中获得的语法糖衣:)
【讨论】:
【参考方案9】:目前最好的选择可能是使用微软的__declspec( property( get=get_func_name, put=put_func_name ) ) PropertyType PropertyName
属性。
但如果您使用其他编译器,则可以使用宏:
#define PROPERTY_GEN(Class, Type, Name, GetMethod, SetMethod) \
class Property_##Name \
public: \
Property_##Name(Class* parent) : _parent(parent) \
Type operator = (Type value) \
\
_parent->SetMethod(value); \
return _parent->GetMethod(); \
\
operator Type() const \
\
return static_cast<const Class*>(_parent)->GetMethod(); \
\
Property_##Name& operator =(const Property_##Name& other) \
\
operator=(other._parent->GetMethod()); return *this; \
; \
Property_##Name(const Property_##Name& other) = delete; \
private: \
Class* _parent; \
Name this ;
// PROPERTY - Declares a property with the default getter/setter method names.
#define PROPERTY(Class, Type, Name) \
PROPERTY_GEN(Class, Type, Name, get_##Name, set_##Name)
然后像这样使用它们:
class SomeClass
public:
PROPERTY(SomeClass, int, Value)
int get_Value() const return _value;
void set_Value(int value) _value = value;
private:
int _value = 0;
;
int main()
SomeClass s, c;
s.Value = 5;
c.Value = 3 * s.Value;
s.Value = c.Value;
您还可以为只读、只写属性和只读非常量 getter 添加其他宏变体。为了能够通过 -> 访问子属性,您可以在宏中添加 operator-> 重载。
与 microsoft 的 __declspec(property(...)) 相比,getter 和 setter 方法可以设为私有,但这并不是真正的优势,因为客户端有时可能需要获取 getter/setter 的地址。 每个属性都有一个额外的 _parent 变量还有一个缺点,如果使用父类,您需要显式定义复制构造函数。
【讨论】:
【参考方案10】:我意识到这个问题可能太老了,无法添加另一个答案,但为了扩展 Moo-Juice 的答案,我想出了一个非常简洁的解决方案:
/// Utility for functions get, set & ptr.
template<typename TVal>
using GetFn = std::function<const TVal& (void)>;
template<typename TVal>
using SetFn = std::function<void(const TVal&)>;
template<typename TVal>
using PtrFn = std::function<TVal* (void)>;
/// The property class and each specialization utility.
template<typename TVal, bool Delegate, bool ReadOnly>
class Property;
template<typename TVal>
using PropertyGetSet = Property<TVal, false, false>;
template<typename TVal>
using PropertyDelGetSet = Property<TVal, true, false>;
template<typename TVal>
using PropertyGet = Property<TVal, false, true>;
template<typename TVal>
using PropertyDelGet = Property<TVal, true, true>;
/// <summary>
/// Property get-set.
/// </summary>
/// <typeparam name="TVal">Value type.</typeparam>
template<typename TVal>
class Property<TVal, false, false>
public:
typedef TVal Value;
Property(const TVal& val)
: m_value(val)
inline const TVal& Get() const return m_value;
inline void Set(const TVal& val) m_value = val;
inline TVal* Ptr() return &m_value;
private:
TVal m_value;
;
/// <summary>
/// Property delegate get-set.
/// </summary>
/// <typeparam name="TVal">Value type.</typeparam>
template<typename TVal>
class Property<TVal, true, false>
public:
typedef TVal Value;
Property(GetFn<TVal> getFn, SetFn<TVal> setFn, PtrFn<TVal> ptrFn)
: m_getFn(getFn)
, m_setFn(setFn)
, m_ptrFn(ptrFn)
inline const TVal& Get() const return m_getFn();
inline void Set(const TVal& val) m_setFn(val);
inline TVal* Ptr() return m_ptrFn();
private:
GetFn<TVal> m_getFn;
SetFn<TVal> m_setFn;
PtrFn<TVal> m_ptrFn;
;
/// <summary>
/// Property get.
/// </summary>
/// <typeparam name="TVal">Value type.</typeparam>
template<typename TVal>
class Property<TVal, false, true>
public:
typedef TVal Value;
Property(const TVal& val)
: m_value(val)
inline const TVal& Get() const return m_value;
inline TVal* Ptr() return &m_value;
private:
TVal m_value;
;
/// <summary>
/// Property delegate get.
/// </summary>
/// <typeparam name="TVal">Value type.</typeparam>
template<typename TVal>
class Property<TVal, true, true>
public:
typedef TVal Value;
Property(GetFn<TVal> getFn, PtrFn<TVal> ptrFn)
: m_getFn(getFn)
, m_ptrFn(ptrFn)
inline const TVal& Get() const return m_getFn();
inline TVal* Ptr() return m_ptrFn();
private:
GetFn<TVal> m_getFn;
PtrFn<TVal> m_ptrFn;
;
然后使用它:
PropertyGetSet<std::string> strGetSet = PropertyGetSet<std::string>("GetSet");
std::string m_strGetSet = "DelGetSet";
PropertyDelGetSet<std::string> strDelGetSet =
PropertyDelGetSet<std::string>(
[&]() -> const std::string& return m_strGetSet; ,
[&](const std::string& val) m_strGetSet = val; ,
[&]() return &m_strGetSet; /* throw? */ );
// The get (read-only) version is the same but without the set function
一些注意事项:
get 函数返回一个 const&,因此您不能使用它来更改值,这是设计使然,因为它允许人们使用引用来设置值,而不是显式 Set,这具有了解的优势当值被设置时。
get-set-ptr 函数没有语法糖,就个人而言,我不喜欢使用运算符,因为它使底层系统更加迟钝,所以使用显式函数让用户知道它是一个属性而不是什么东西别的。但如果可以的话,你可以添加一些运算符重载。
所有特化都有一个 Ptr 函数,它将作为数据的指针。但是,在使用委托版本时,您可以选择 throw,因此任何尝试使用它的人都必须解决它。它存在的原因是,在最坏的情况下,您可能会尝试将指针用于非常特殊的情况,我强烈建议不要使用它,因此请随意删除它或对其进行额外的专业化。
最后,它有点冗长,您可以将用法包装在一个宏中以使语法更短一些,但就个人而言,我喜欢它的方式,因为它更明确。
编辑: 您可能会遇到与此设计相同的问题,请查看以下链接以了解该问题以及我提出的解决方案:https://***.com/a/68563492/3339838
【讨论】:
【参考方案11】:不,没有。您只需创建 getter 和 setter 函数:
someThing.setText("blah");
std::string blah = someThing.getText();
【讨论】:
以上是关于本机 C++ 中的类 C# 属性?的主要内容,如果未能解决你的问题,请参考以下文章
在退出之前跟踪 - 并正确结束 - C# - C++/CLI - C++ Windows 窗体应用程序中的本机和托管线程
从本机 C++ 调用 C#,而不使用 /clr 或 COM?