Есть ли у C ++ 11 свойства стиля C #?


93

В C # есть хороший синтаксический сахар для полей с геттером и сеттером. Кроме того, мне нравятся автоматически реализованные свойства, которые позволяют мне писать

public Foo foo { get; private set; }

На C ++ мне нужно написать

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

Есть ли такая концепция в C ++ 11, позволяющая мне добавить немного синтаксического сахара к этому?


62
Это можно сделать с помощью пары макросов. убегает от стыда
Манкарс

7
@Eloff: Обнародовать все - ВСЕГДА плохая идея.
Kaiserludi 08

8
Нет такого понятия! И вам это тоже не нужно: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
а) этот вопрос довольно старый б) я просил синтаксического сахара, который позволил бы мне избавиться от круглых скобок в) хотя в статье представлены веские аргументы против адаптации свойств, независимо от того, нужны ли свойства C ++ или нет очень субъективно. C ++ является эквивалентом Touring-machine даже без них, но это не означает, что наличие такого синтаксического сахара сделало бы C ++ более продуктивным.
Radim Vansa

3
Точно нет.

Ответы:


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;
};

Вы можете написать свои собственные геттеры и сеттеры на месте, и если вам нужен доступ к члену класса-держателя, вы можете расширить этот пример кода.


1
Любые идеи о том, как изменить этот код, чтобы по-прежнему иметь частную переменную-член, к которой Foo может получить доступ изнутри, в то время как общедоступный API только предоставляет свойство? Я мог бы, конечно, просто сделать Foo другом альфа / бета, но тогда мне все равно пришлось бы написать alpha.value, чтобы получить доступ к значению, но я бы предпочел, чтобы прямой доступ к переменной-члену изнутри Foo был больше похож на доступ к члену самого Foo, а не к члену специального вложенного класса свойств.
Kaiserludi

1
@Kaiserludi Да: в этом случае сделайте альфу и браво приватными. В Foo вы можете читать / писать с указанными выше «свойствами», но за пределами Foo это было бы невозможно. Чтобы обойти это, сделайте общедоступную ссылку на константу. Он доступен извне, но только для чтения, так как это постоянная ссылка. Единственное предостережение - вам понадобится другое имя для общедоступной ссылки на константу. Я бы лично использовал _alphaдля частной переменной и alphaдля справки.
Kapichu 05

1
@Kapichu: не решение по 2 причинам. 1) В C # средства получения / установки свойств часто используются для встраивания проверок безопасности, которые принудительно выполняются для публичных пользователей класса, позволяя функциям-членам напрямую обращаться к значению. 2) ссылки на константы не бесплатны: в зависимости от компилятора / платформы они будут увеличиваться sizeof(Foo).
ceztko

@psx: из-за ограничений этого подхода я бы избегал его и ждал надлежащего дополнения к стандарту, если оно когда-либо появится.
ceztko

@Kapichu: Но альфа и браво в примере кода - это свойства. Я хотел бы получить прямой доступ к самой переменной изнутри реализации Foo, без необходимости использовать свойство, в то время как я хотел бы предоставить доступ только через свойство в API.
Kaiserludi

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;

Чтобы реализовать собственный метод получения / установки, просто наследовать:

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
Логика «пользовательского метода получения / установки» может быть синтаксически более чистой с помощью лямбда-функций, к сожалению, вы не можете определить лямбда-выражение вне исполняемого контекста в C ++ (пока!), Поэтому без использования макроса препроцессора вы получите код, который просто так же неудобно, как и тупые геттеры / сеттеры, что прискорбно.
Дай

2
"Class: ..." в последнем примере интересен и отсутствует в других примерах. Он создает необходимое объявление-друга - без введения нового имени класса.
Ханс Олссон

Большая разница между этим ответом и ответом от 19 ноября 2010 г. заключается в том, что он позволяет переопределить геттер или сеттер в каждом конкретном случае. Таким образом можно было проверить, что ввод находится в диапазоне, или отправить уведомление об изменении, чтобы изменить прослушиватели событий, или место, где можно повесить точку останова.
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;  
}

2
Это также работает с clang
Passer By

18

Вы можете до некоторой степени эмулировать геттер и сеттер, имея член выделенного типа и переопределяя operator(type)и operator=для него. Хорошая ли это идея - другой вопрос, и я собираюсь +1ответить Керреку С.Б., чтобы высказать свое мнение по этому поводу :)


Вы можете эмулировать вызов метода при назначении или чтении по такому типу, но вы не можете различить, кто вызывает операцию назначения (чтобы запретить ее, если это не владелец поля) - что я пытаюсь сделать, указав другой доступ уровень для геттера и сеттера.
Radim Vansa

@Flavius: Просто добавьте friendк владельцу поля.
kennytm

17

Может быть, взгляните на класс свойств, который я собрал за последние часы: /codereview/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

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::*значит? Я никогда раньше не видел ничего подобного?
Rika

1
Это указатель на нестатическую функцию-член в классе C. Это похоже на простой указатель на функцию, но для вызова функции-члена вам необходимо предоставить объект, для которого функция вызывается. Это достигается с помощью строки itsObject->*itsSetter(theValue)в примере выше. См. Здесь более подробное описание этой функции.
Christoph Bö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. Вся их система была основана на том, что я бы назвал злоупотреблением собственностью. Если бы они использовали методы вместо свойств, думаю, я бы гораздо быстрее понял, что пошло не так. Если бы они использовали поля (или, по крайней мере, сделали бы их свойства более похожими на поля), я думаю, что систему было бы легче расширять и поддерживать.

[Edit] Изменил свои мысли. У меня был плохой день, и я немного рассердился. Эта очистка должна быть более профессиональной.


11

На основе https://stackoverflow.com/a/23109533/404734 вот версия с публичным получателем и частным сеттером:

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

4

Это не совсем свойство, но оно делает то, что вы хотите, простым способом:

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

Здесь большой X ведет себя как public int X { get; private set; }в синтаксисе C #. Если вам нужны полноценные свойства, я сделал первую попытку реализовать их здесь .


2
Это плохая идея. Всякий раз, когда вы делаете копию объекта этого класса, ссылка Xна новый объект по-прежнему будет указывать на член старого объекта, потому что он просто копируется как член-указатель. Это плохо само по себе, но когда старый объект удаляется, поверх него появляется повреждение памяти. Чтобы это работало, вам также нужно будет реализовать собственный конструктор копирования, оператор присваивания и конструктор перемещения.
toster 03

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), но ваши аксессоры вообще не имеют ничего общего со свойствами ...
Маттиас

@Matthias Заявив, что это не имеет никакого отношения к свойствам, мне ничего не говорит, вы можете уточнить? кроме того, я не пытался их сравнивать, но если вам нужен мутатор и аксессуар, вы можете использовать это соглашение.
Сольник

Речь идет о программной концепции Properties. Свойства можно использовать, как если бы они были общедоступными элементами данных (использование), но на самом деле они являются специальными методами, называемыми средствами доступа (объявление). Ваше решение придерживается методов (обычных геттеров / сеттеров) для объявления и использования. Так что, во-первых, это определенно не то использование, о котором просит OP, а скорее какое-то странное и нетрадиционное соглашение об именах (и, следовательно, во-вторых, также никакого синтаксического сахара).
Матиас

В качестве незначительного побочного эффекта ваш мутатор неожиданно работает как свойство, поскольку в C ++ один из них лучше всего инициализируется, foo(bar)а foo=barне с помощью void foo(Bar bar)метода мутатора для _fooпеременной-члена.
Матиас

@Matthias Я знаю, что такое свойства, я довольно много писал на C ++ и на C # уже более десяти лет, я не спорю о преимуществах свойств и о том, что они из себя представляют, но я ДЕЙСТВИТЕЛЬНО не нуждался в них в C ++, на самом деле вы говорят, что их можно использовать как общедоступные данные, и это в основном правда, но в C # бывают случаи, когда вы даже не можете использовать свойства напрямую, например, передача свойства по ссылке, тогда как с общедоступным полем вы можете.
Сольник

4

Нет ... Но вы должны подумать, если это просто функция get: set и никакие дополнительные задачи не выполняются внутри методов get: set, просто сделайте ее общедоступной.


2

Я собрал идеи из нескольких источников C ++ и поместил их в красивый, все еще довольно простой пример для геттеров / сеттеров на C ++:

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

Я никогда не могу поклониться. Я думаю, что не все функции подходят. Для меня объекты - это гораздо больше, чем пара функций установки и получения. Я пробовал собственные реализации, позволяющие избежать ненужных накладных расходов на постоянную память, но синтаксис и рутинная работа по объявлению экземпляров были неудовлетворительными; Меня привлекали декларативные макросы, но я все равно не большой поклонник макросов. И, наконец, мой подход привел к свойствам, доступным с помощью синтаксиса функций, которые многие, включая меня, не одобряют.
Red.Wave

0

Действительно ли вашему классу нужно применять какой-то инвариант или это просто логическая группировка элементов-членов? Если это последнее, вам следует подумать о том, чтобы создать структуру и получить прямой доступ к членам.


0

Есть набор макросов , написанных здесь . В нем есть удобные объявления свойств для типов значений, ссылочных типов, типов только для чтения, сильных и слабых типов.

class MyClass {

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

 public:
 ...

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.