本机 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 中,属性是在幕后发出的真正 getset 函数的语法糖(实际上它们不仅仅是语法糖,因为属性在生成的 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&lt;std::basic_string&lt;char&gt; &gt;’ has no member named ‘c_str’) std::cout &lt;&lt; a.text 也不会编译 (template argument deduction/substitution failed)

我建议对template&lt;typename T&gt; 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 属性。

clang 也支持, 编译时会转换为您的 getter/setter(不会添加任何新变量), 在使用中,它是最接近真实财产的事物(可以访问财产的财产......)。

但如果您使用其他编译器,则可以使用宏:

#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++

在退出之前跟踪 - 并正确结束 - C# - C++/CLI - C++ Windows 窗体应用程序中的本机和托管线程

从本机 C++ 调用 C#,而不使用 /clr 或 COM?

调试信息(断点等)存储在 VS2013、本机 C++ dll 项目中的位置在哪里?

C++ 中的类属性变量

Visual Studio 2008 中的 C# 项目中的 C++ 项目参考