Чи має С ++ 11 властивості стилю C #?


93

У C # є симпатичний синтаксичний цукор для полів з геттером та сеттером. Більше того, мені подобаються автореалізовані властивості, які дозволяють мені писати

public Foo foo { get; private set; }

У C ++ я повинен писати

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

Чи є така концепція в C ++ 11, яка дозволяє мені мати синтаксичний цукор на цьому?


62
Це можна зробити з парою макросів. втікає від сорому
Манкарсе

7
@Eloff: Просто оприлюднювати все це ЗАВЖДИ погана ідея.
Кайзерлуді

8
Немає такої концепції! І вам це теж не потрібно: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
а) це запитання досить старе; б) я просив синтаксичний цукор, який би дозволив мені позбутися дужок; є дуже суб'єктивним. C ++ є еквівалентом Touring-машини навіть без них, але це не означає, що наявність такого синтаксису цукру зробить C ++ більш продуктивним.
Радім Ванса

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 виставляє лише властивість? Я, звичайно, міг просто зробити Фу другом альфа / бета, але тоді мені все одно довелося б писати alpha.value, щоб отримати доступ до значення, але я волів би, якби безпосередній доступ до змінної члена зсередини Foo був би більш схожим доступ до члена самого Foo, а не до члена спеціального вкладеного класу властивостей.
Кайзерлуді

1
@Kaiserludi Так: у цьому випадку зробіть альфа та браво приватними. У Foo ви можете читати / писати з тими "властивостями" вище, але за межами Foo це вже неможливо. Щоб обійти це, зробіть довідку про const, яка є загальнодоступною. Він доступний ззовні, але лише для читання, оскільки є постійною посиланням. Єдине застереження полягає в тому, що вам знадобиться інша назва для публічної довідки про const. Я особисто використовував би _alphaдля приватної змінної та alphaдля довідки.
Капічу

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

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

@Kapichu: Але альфа та браво у прикладі коду є властивостями. Я хотів би безпосередньо отримати доступ до самої змінної зсередини реалізації Foo, не потребуючи використання властивості, тоді як я хотів би відкрити доступ лише через властивість в API.
Кайзерлуді

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

Ви можете визначити щось із зазначеного в макросах, щоб зробити його більш стислим.


Мені цікаво, якщо це компілюється до функції з нульовою вартістю, я не знаю, чи загортання кожного члена даних в екземпляр класу призведе до того ж типу упаковки структур, наприклад.
Dai

1
Логіка "користувацького геттера / сеттера" може бути синтаксично чистішою завдяки використанню лямбда-функцій, на жаль, ви не можете визначити лямбду поза виконуваним контекстом у C ++ (поки що!), Тому без використання макросу препроцесора ви отримуєте код, який просто так само прискіпливо, як німі геттери / сетери, що шкода.
Dai

2
"Клас: ..." в останньому прикладі цікавий і відсутній в інших прикладах. Він створює необхідну декларацію друга - без введення нової назви класу.
Ганс Олссон,

Велика різниця між цією відповіддю та відповідями від 19 листопада 2010 року полягає в тому, що це дозволяє замінювати геттер або сеттер у кожному конкретному випадку. Таким чином, можна перевірити вхідні дані, щоб вони знаходились у зоні дії, або відправити сповіщення про зміну для зміни слухачів подій, або місце, де можна повісити точку зупинки.
Eljay

Зауважте, що virtualце, мабуть, непотрібно для більшості випадків використання, оскільки властивість навряд чи буде використовуватися поліморфно.
Елджей

28

У мові С ++ немає нічого, що працювало б на всіх платформах та компіляторах.

Але якщо ви готові порушити крос-платформну сумісність і прив'язатись до певного компілятора, ви можете скористатися таким синтаксисом, наприклад, у 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

Ви можете до певної міри емулювати getter та setter, маючи член виділеного типу та перевизначення operator(type)та operator=для нього. Це хороша ідея - це інше питання, і я +1перейду до відповіді Керрека С.Б., щоб висловити свою думку щодо цього :)


Ви можете емулювати виклик методу при призначенні або читанні з такого типу, але ви не можете розрізнити, хто викликає операцію присвоєння (заборонити це, якщо це не власник поля) - те, що я намагаюся зробити, вказавши інший доступ рівень для геттера та сетера.
Радім Ванса

@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);

Хороший, однак, хоча це дозволяє визначені користувачем засоби доступу, я не шукаю функції публічного геттера / приватного сеттера.
Радім Ванса

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

І ось шаблон класу "Властивості".

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
Це вказівник на нестатичну функцію-член у класі C. Це схоже на звичайний покажчик функції, але для виклику функції-члена вам потрібно надати об'єкт, на якому функція викликається. Це досягається за допомогою рядка itsObject->*itsSetter(theValue)у прикладі вище. Дивіться тут для більш детального опису цієї функції.
Крістоф Беме

@Niceman, чи є приклад використання? Здається, бути членом є дуже дорого. Це не особливо корисно як статичний член.
Грим Фанданго

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на новий об'єкт все одно вказуватиме на член старого об'єкта, оскільки він просто копіюється як член вказівника. Сам по собі це погано, але коли старий об’єкт видаляється, поверх нього з’являється пошкодження пам’яті. Щоб зробити це, вам доведеться також реалізувати власний конструктор копіювання, оператор присвоєння та конструктор переміщення.
тостер

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 # є випадки, коли ви навіть не можете використовувати властивості безпосередньо, наприклад, передавати властивість за допомогою ref, тоді як із загальнодоступним полем ви можете.
Еял Сольник

4

Ні .. Але слід врахувати, чи просто це: функція set і не додаткове завдання, попередньо сформоване всередині get: встановлені методи просто оприлюднюють її.


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


Існує накладні витрати на пам'ять для збереження саморефлексій, + синтаксис akward ctor.
Red.Wave

Запропонувати властивості? Я здогадуюсь, було купу відхилених таких пропозицій.
Red.Wave

@ Red.Wave Поклоніться лорду і господареві відхилення. Ласкаво просимо до C ++. Clang та MSVC мають спеціальні розширення для властивостей, якщо ви не хочете самостійних посилань.
lama12345

Я ніколи не можу вклонитися. Думаю, не кожна функція навіть відповідна. Для мене Об'єкти - це набагато більше, ніж пара функцій setter + getter. Я пробував власні реалізації, уникаючи непотрібних постійних накладних витрат на пам'ять, але синтаксис і завдання декларування екземплярів не були задовільними; Мене потягло використання декларативних макросів, але я все одно не є великим шанувальником макросів. І, нарешті, мій підхід призвів до недосконалості, до якої доступний синтаксис функції, яку багато хто, зокрема я, не схвалюють.
Ред. Хвиля

0

Чи потрібно класу дійсно застосовувати якийсь інваріант або це лише логічне групування елементів-членів? Якщо це останнє, вам слід подумати про те, щоб зробити річ структурою та отримати прямий доступ до членів.


0

Є набір макросів , написаних тут . Це декларації про властивості для типів значень, типів посилань, типів зчитування лише типів, сильних і слабких типів.

class MyClass {

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

 public:
 ...

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.