cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Добавить свойства в C++ #521

Open klappdev opened 2 years ago

klappdev commented 2 years ago

При работе с классами, хорошей практикой, является объявление полей закрытыми. Для чтения значения полей из вне, в классе объявляют геттеры. А для записи объявляют сеттеры.

Возьмём для примера класс QPoint с фреймворка Qt.

class QPoint {
public:
    // construct, dectructor, other
    inline int x() const { return xp; }
    inline int y() const { return yp; }
    inline void setX(int x) { this.xp = x; }
    inline void setY(int y) { this.yp = y; } 

private:
   int xp;
   int yp;
};

https://code.woboq.org/qt5/qtbase/src/corelib/tools/qpoint.h.html

При этом в большинстве случаев, геттеры и сеттеры очень простые. И не хочется писать руками.

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

template<typename T>
class Property {
public:
    constexpr explicit Property(const T& value) : value(value) {}

    constexpr Property& operator=(const T& newValue) {
        this->value = newValue;
        return *this;
    }

    constexpr operator const T&() const { return value; }
private:
    T value;
};

class QPoint {
public:
    // construct, dectructor, other

   Property<int> x;
   Property<int> y;
};

https://github.com/klappdev/firearrow/blob/master/app/src/main/cpp/util/property/Property.hpp https://github.com/klappdev/firearrow/blob/master/app/src/main/cpp/util/property/Getter.hpp https://github.com/klappdev/firearrow/blob/master/app/src/main/cpp/util/property/Setter.hpp

Концепция свойств(геттеров, сеттеров) существует во многих языках, таких например как Swift, Kotlin, C# и д.р., на уровне языка. Хочется что б в С++ завезли такую возможность на уровне языка. Например, это могло выглядить как в С#.

class QPoint {
public:
    // construct, dectructor, other
private:  
   int x { set; get; }; 
   int y { set; get; };
};

Предлагаю добавить два контекстных ключевых слова - set, get. Которые были бы ключевыми словами, только при объявлении поля класса. По аналогии с обычными методам, можно бы объявлять с разными квалификаторами - &, &&, const, volatile, noexcept. В случае если нужно не тривиальный геттер, сеттер, то код мог бы выглядеть следующим образом.

class QPoint {
public:
    // construct, dectructor, other
private:  
   int x { 
      set(int value) {
          std::cout << "invoke setter - x" << std::endl; 
          this->x = value;
      }; 
      get() const {
          std::cout << "invoke getter - x" << std::endl; 
          return x;
      }; 
   }; 
   int y { set; get; };
};

Под капотом, компилятор генерировал те самые методы геттеры, сеттеры. Современный С++ вносит много новых фич, которые позволяет писать меньше шаблонного кода. Концепция свойств(геттеров, сеттеров) как раз позволяет писать меньше кода, и при этом код остается таким же читаемым. Очень хочется видеть в С++.

Полезные ссылки:

pavelkryukov commented 2 years ago

tl;dr: пример и аргументация кажутся неудачными, идею критике подвергать пока не буду.

При работе с классами, хорошей практикой, является объявление полей закрытыми. Возьмём для примера класс QPoint с фреймворка Qt.

Спорный переход: QPoint больше похож на структуру данных (внешний функционал), которую Qt почему-то реализовал как класс. Получился "квазикласс" в терминах этой статьи: http://www.idinews.com/quasiClass.pdf

Концепция свойств(геттеров, сеттеров) существует во многих языках, таких например как Swift, Kotlin, C# и д.р., на уровне языка.

Заметим, что во многих не менее замечательных языках концепция свойств годами отсутствует.

Концепция свойств(геттеров, сеттеров) как раз позволяет писать меньше кода, и при этом код остается таким же читаемым.

Если полей в структуре немного, то и кода для автоматических геттеров/сеттеров нужно немного, особенно с наличием auto и decltype. Если полей в структуре много... то с ней чего-то не то.

class QPoint {
public:
    // construct, dectructor, other
private:  
   int x { 
      set(int value) {
          std::cout << "invoke setter - x" << std::endl; 
          this->x = value;
      }; 
      get() const {
          std::cout << "invoke getter - x" << std::endl; 
          return x;
      }; 
   }; 
   int y { set; get; };
};

В этом примере следующий шаг, который хочется сделать — применить этот геттер/сеттер к y. В этот момент естественно ввести класс QCoordinate, в котором определить operator= и operator int().

pavelkryukov commented 2 years ago
class QPoint {
public:
    // construct, dectructor, other
private:  
   int x { set; get; }; 
   int y { set; get; };
};

Много вопросов, как это будет сочетаться с конструкциями, специфичными для C++.

Раз:

QPoint p;
std::vector<int*> pointers;
pointers.push_back(&p.x); // что это?

Два:

// что будет делать такой код?
auto pointer_to_member = &QPoint::x;
p.*pointer_to_member = 3;

Три:

QPoint::QPoint(int a, int b) : x(a), y(b) { } // вызывать ли сеттер?
pavelkryukov commented 2 years ago

Старые предложения об автоматических геттерах/сеттерах и более общем случае неявных функций, все не позднее 2005 года:

В последнем предложении содержится много критики к решениям уровня языка

klappdev commented 2 years ago

Геттеры/сеттеры на уровне языка работают как буто простые методы. Поэтому не должно конфликтовать с другими частями языка. В С++23 добавили deducing this. Что является тоже пересмотром методов в частности. И позволяет меньше писать шаблонного кода. Поэтому почему не пересмотреть и в сторону свойств.

kelbon commented 2 years ago

Тривиальные геттеры и сеттеры это ошибка проектирования и бесполезный код. В джаву С++ не нужно превращать Сеттер должен делать что-то осмысленное и называться не SetX, например vector::resize/reserve, это вообще говоря в ваших терминах сеттеры, но называются они осмысленно и делают логичные для инкапсуляции логики вещи P.S. в расте есть vec::set_len просто ору

kov-serg commented 1 year ago

Вообще геттеры и сетеры очень удобны если нужно автоматически оповещать об изменении в данных. И на C++ довольно не удобно получать указатель на this класса, который содержит подобные геттеры и сеттеры.