C ++ 11是否具有C#样式的属性?


93

在C#中,使用getter和setter的字段有一个不错的语法糖。此外,我喜欢自动执行的属性,这些属性使我可以编写

public Foo foo { get; private set; }

在C ++中,我必须编写

private:
    Foo foo;
public:
    Foo getFoo() { return foo; }

C ++ 11中是否有这样的概念,可以让我对此有所了解?


62
可以使用几个宏来完成。丢人丢人
Mankarse 2011年

7
@Eloff:仅仅公开一切总是一个坏主意。
Kaiserludi 2014年

8
没有这样的概念!而且您也不需要它:seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout 2015年

2
a)这个问题已经很老了b)我在问语法糖,这将使我摆脱括号c)尽管本文提出了反对改编属性的有效论点,无论C ++是否需要属性是非常主观的。即使没有C ++,C ++也等同于Touring-machine,但这并不意味着拥有这样的语法糖就可以使C ++更具生产力。
Radim Vansa 2015年

3
当然不。

Answers:


87

在C ++中,您可以编写自己的功能。这是使用未命名类的属性的示例实现。维基百科文章

struct Foo
{
    class {
        int value;
        public:
            int & operator = (const int &i) { return value = i; }
            operator int () const { return value; }
    } alpha;

    class {
        float value;
        public:
            float & operator = (const float &f) { return value = f; }
            operator float () const { return value; }
    } bravo;
};

您可以在适当的位置编写自己的getter和setters,如果希望拥有者类成员访问,可以扩展此示例代码。


1
关于如何修改此代码以仍然具有Foo可以在内部访问的私有成员变量的任何想法,而公共API仅公开该属性?我当然可以只让Foo成为alpha / beta的朋友,但是那时我仍然必须编写alpha.value来访问值,但是我宁愿直接从Foo内部访问成员变量感觉像访问Foo本身的成员,而不是特殊的嵌套属性类的成员。
Kaiserludi 2014年

1
@Kaiserludi是:在这种情况下,请将Alpha和Bravo设为私有。在Foo中,您可以使用上面的“属性”进行读取/写入,但是在Foo之外,这将不再可能。为了避免这种情况,请公开引用一个const引用。从外部可以访问它,但是由于它是一个常量引用,因此只能读取。唯一需要注意的是,公共const引用需要另一个名称。我个人将使用_alpha私有变量和alpha参考。
卡皮丘2015年

1
@Kapichu:不是解决方案,有两个原因。1)在C#中,属性获取器/设置器通常用于嵌入安全性检查,这些检查强制给类的公共用户使用,同时允许成员函数直接访问该值。2)const引用不是免费的:根据编译器/平台的不同,它们会放大sizeof(Foo)
ceztko 2015年

@psx:由于这种方法的局限性,我会避免使用它,并等待该标准的出现(如果有的话)。
ceztko 2015年

@Kapichu:但是示例代码中的alpha和bravo是属性。我想直接从Foo的实现内部访问变量本身,而不需要使用属性,而我只想通过API中的属性公开访问。
Kaiserludi 2015年

53

C ++没有内置此功能,您可以定义一个模板来模拟属性功能:

template <typename T>
class Property {
public:
    virtual ~Property() {}  //C++11: use override and =default;
    virtual T& operator= (const T& f) { return value = f; }
    virtual const T& operator() () const { return value; }
    virtual explicit operator const T& () const { return value; }
    virtual T* operator->() { return &value; }
protected:
    T value;
};

定义属性

Property<float> x;

要实现自定义getter / setter,只需继承:

class : public Property<float> {
    virtual float & operator = (const float &f) { /*custom code*/ return value = f; }
    virtual operator float const & () const { /*custom code*/ return value; }
} y;

要定义一个只读属性

template <typename T>
class ReadOnlyProperty {
public:
    virtual ~ReadOnlyProperty() {}
    virtual operator T const & () const { return value; }
protected:
    T value;
};

在课堂Owner使用它

class Owner {
public:
    class : public ReadOnlyProperty<float> { friend class Owner; } x;
    Owner() { x.value = 8; }
};

您可以在宏中定义一些上述内容,以使其更加简洁。


我很好奇这是否可以简化为零成本功能,例如,我不知道是否将每个数据成员包装在类实例中是否会导致相同的结构打包。

1
使用lambda函数可以使“自定义getter / setter”逻辑在语法上更简洁,不幸的是,您不能在C ++的可执行上下文之外定义lambda(还!),因此,如果不使用预处理器宏,最终得到的代码仅仅是像傻傻的吸气剂/设置器一样愚蠢,这是不幸的。
Dai)

2
最后一个示例中的“ class:...”很有趣,而其他示例中则没有。它创建了必要的朋友声明-无需引入新的类名。
汉斯·奥尔森,

此答案与2010年11月19日的答案之间的最大区别是,这使得可以逐案覆盖getter或setter。这样一来,您可以审查输入是否在范围内,或者发送更改通知以更改事件侦听器,或者挂断点的位置。
Eljay

请注意,virtual可能大多数用例是不必要的,因为房产是不太可能使用多态。
埃杰

28

C ++语言没有任何东西可以在所有平台和编译器上正常工作。

但是,如果您愿意破坏跨平台的兼容性并承诺使用特定的编译器,则可以使用这种语法,例如在Microsoft Visual C ++中,您可以

// declspec_property.cpp  
struct S {  
   int i;  
   void putprop(int j) {   
      i = j;  
   }  

   int getprop() {  
      return i;  
   }  

   __declspec(property(get = getprop, put = putprop)) int the_prop;  
};  

int main() {  
   S s;  
   s.the_prop = 5;  
   return s.the_prop;  
}


18

通过具有专用类型的成员并对其进行重写operator(type)operator=可以在某种程度上模拟getter和setter 。这是否是一个好主意是另一个问题,我将在+1Kerrek SB的回答中表达我的意见:)


您可以通过这种类型模拟在分配或读取时调用方法,但无法区分是谁调用了assign操作(如果不是字段所有者,则禁止该操作)-我试图通过指定不同的访问权限来做吸气和塞特的水平。
Radim Vansa

@Flavius:只需将一个添加friend到字段所有者。
kennytm 2011年

17

也许看看我在过去几个小时内组装的属性类:https : //codereview.stackexchange.com/questions/7786/c11-feedback-on-my-approach-to-c-like-class-properties

它允许您具有如下行为的属性:

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);

很好,但是尽管这允许用户定义的访问器,但我没有想要的公共获取器/私有设置器功能。
Radim Vansa 2012年

17

使用C ++ 11,您可以定义一个Property类模板并像这样使用它:

class Test{
public:
  Property<int, Test> Number{this,&Test::setNumber,&Test::getNumber};

private:
  int itsNumber;

  void setNumber(int theNumber)
    { itsNumber = theNumber; }

  int getNumber() const
    { return itsNumber; }
};

这里是Property类模板。

template<typename T, typename C>
class Property{
public:
  using SetterType = void (C::*)(T);
  using GetterType = T (C::*)() const;

  Property(C* theObject, SetterType theSetter, GetterType theGetter)
   :itsObject(theObject),
    itsSetter(theSetter),
    itsGetter(theGetter)
    { }

  operator T() const
    { return (itsObject->*itsGetter)(); }

  C& operator = (T theValue) {
    (itsObject->*itsSetter)(theValue);
    return *itsObject;
  }

private:
  C* const itsObject;
  SetterType const itsSetter;
  GetterType const itsGetter;
};

2
什么C::*意思 我以前从未见过类似的东西吗?
里卡

1
它是指向class中的非静态成员函数的指针C。这类似于普通函数指针,但是要调用成员函数,您需要提供一个在其上调用函数的对象。这是通过itsObject->*itsSetter(theValue)上面示例中的行实现的。有关此功能的详细说明,请参见此处
ChristophBöhme,

@Niceman,有用法示例吗?拥有一个成员似乎非常昂贵。作为静态成员,它也不是特别有用。
Grim Fandango

16

正如许多其他人已经说过的那样,该语言没有内置支持。但是,如果您以Microsoft C ++编译器为目标,则可以利用此处介绍的Microsoft特定属性扩展

这是链接页面中的示例:

// declspec_property.cpp
struct S {
   int i;
   void putprop(int j) { 
      i = j;
   }

   int getprop() {
      return i;
   }

   __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main() {
   S s;
   s.the_prop = 5;
   return s.the_prop;
}

12

不,C ++没有属性的概念。尽管定义和调用getThis()或setThat(value)可能很尴尬,但是您正在向这些方法的使用者声明可能会发生某些功能。另一方面,访问C ++中的字段会告诉使用者不会发生任何其他功能。属性使这一点变得不那么明显,因为乍一看属性访问似乎像一个字段一样反应,但实际上像一个方法一样反应。

顺便说一句,我正在一个.NET应用程序(一个非常著名的CMS)中尝试创建一个客户会员系统。由于他们为用户对象使用属性的方式,动作无法正常执行,这导致我的实现以奇怪的方式执行,包括无限递归。这是因为他们的用户对象在尝试访问诸如StreetAddress之类的简单内容时调用了数据访问层或某些全局缓存系统。他们的整个系统建立在所谓的滥用财产的基础上。如果他们使用方法而不是属性,我想我会更快地找出问题所在。如果他们使用了字段(或者至少使它们的属性表现得更像字段),我认为该系统将更易于扩展和维护。

[编辑]改变了我的想法。我度过了糟糕的一天,然后咆哮了一下。此清理工作应该更专业。



4

这不完全是一个属性,但是它可以按照您想要的简单方式进行操作:

class Foo {
  int x;
public:
  const int& X;
  Foo() : X(x) {
    ...
  }
};

在这里,大X的行为类似于public int X { get; private set; }C#语法。如果您想要完善的属性,我首先在这里实现它们。


2
这不是一个好主意。每当您复制此类的对象时,引用X,对新对象仍将指向旧对象的成员,因为它就像指针成员一样被复制。这本身是不好的,但是当删除旧对象时,您会在上面发生内存损坏。为了使这项工作有效,您还必须实现自己的副本构造函数,赋值运算符和move构造函数。
toster

4

您可能知道这一点,但我只需执行以下操作:

class Person {
public:
    std::string name() {
        return _name;
    }
    void name(std::string value) {
        _name = value;
    }
private:
    std::string _name;
};

这种方法很简单,不需要任何巧妙的技巧,就可以完成工作!

但是,问题是有些人不喜欢在下划线前给私有字段加上前缀,因此他们不能真正使用此方法,但是幸运的是,对于那些这样做的人,这确实很简单。:)

get和set前缀并没有使您的API更加清晰,但使它们更加冗长,而我认为它们没有添加有用的信息的原因是,当某人需要使用API​​时,如果该API有意义,那么她可能会意识到它的含义。没有前缀。

还有一件事,因为name不是动词,所以很容易理解它们是属性。

最坏的情况是,如果API是一致的,并且用户没有意识到这name()是访问者,并且name(value)而是一个变种器,那么她只需在文档中查找一次即可了解该模式。

尽管我喜欢C#,但我认为C ++根本不需要属性!


如果使用某个变量foo(bar)(而不是较慢的变量foo = bar),那么您的mutators就很有意义,但是您的访问者与属性完全无关...
Matthias

@Matthias声明它与属性完全没有关系,什么也没告诉我,您能详细说明一下吗?此外,我没有尝试比较它们,但是如果您需要一个mutator和accessor,则可以使用此约定。
Eyal Solnik '17

问题是关于属性的软件概念。可以像使用公共属性一样使用属性,但实际上它们是称为访问器(声明)的特殊方法。您的解决方案坚持声明和用法的方法(普通getter / setter方法)。因此,首先,这绝对不是OP所要求的用法,而是某种奇怪且非常规的命名约定(因此,第二,也没有语法糖)。
Matthias

作为次要的副作用,您的mutator令人惊讶地充当了Property,因为在C ++中,最好的初始化方法是用成员变量的mutator方法foo(bar)代替foo=bar它。void foo(Bar bar)_foo
Matthias

@Matthias我知道什么是属性,十年来我一直在用C#和C#编写很多代码,我不是在谈论属性的好处以及它们的含义,但我从来没有真正在C ++中使用它们,事实上, '说它们可以用作公共数据,这在大多数情况下是正确的,但是在C#中,您甚至不能直接使用属性,例如通过ref传递属性,而在公共字段中则可以。
Eyal Solnik '17

4

不,但是您应该考虑它是否只是get:set函数,而没有在get:set方法中执行的其他任务只是将其公开。


2

我从多个C ++来源收集了这些想法,并将其放入C ++中的getter / setter方法的漂亮但仍然非常简单的示例中:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

输出:

new canvas 256 256
resize to 128 256
resize to 128 64

您可以在此处在线测试:http : //codepad.org/zosxqjTX


保持自我参考存在内存开销,还有一个笨拙的ctor语法。
Red.Wave

提出物业?我想有很多这样的建议被拒绝了。
Red.Wave

@ Red.Wave然后向主人和拒绝的主人鞠躬。欢迎使用C ++。如果您不想使用自我引用,则Clang和MSVC具有属性的自定义扩展名。
lama12345

我永远不能低头。我认为并非每个功能都适用。对我来说,对象不仅仅是一对setter + getter函数。我尝试了自己的实现,以避免不必要的永久内存开销,但是声明实例的语法和繁琐的工作并不令人满意。我被吸引使用声明性宏,但是无论如何我都不是宏的忠实拥护者。最后,我的方法导致使用函数语法访问的函数,包括我在内的许多人都不赞成。
Red.Wave19年

0

您的类是否真的需要强制执行一些不变性,还是仅仅是成员元素的逻辑分组?如果是后者,则应考虑将事物构造为结构并直接访问成员。


0

有一组书面宏这里。这具有用于值类型,引用类型,只读类型,强类型和弱类型的方便的属性声明。

class MyClass {

 // Use assign for value types.
 NTPropertyAssign(int, StudentId)

 public:
 ...

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.