Я преобразовывал структуру в класс, таким образом, я мог осуществить интерфейс метода set для своих переменных.
Я не хотел изменять все экземпляры, где переменная была считана, все же. Таким образом, я преобразовал это:
struct foo_t {
int x;
float y;
};
к этому:
class foo_t {
int _x;
float _y;
public:
foot_t() : x(_x), y(_y) { set(0, 0.0); }
const int &x;
const float &y;
set(int x, float y) { _x = x; _y = y; }
};
Я интересуюсь этим, потому что это, кажется, моделирует идею C# общедоступных свойств только для чтения.
Прекрасные компиляции, и я еще не видел проблем.
Помимо шаблона соединения ссылок константы в конструкторе, что оборотные стороны к этому методу?
Какие-либо странные проблемы искажения?
Почему я не видел эту идиому прежде?
Существует проблема сглаживания, поскольку вы раскрываете ссылку на внутренние данные foo_t
, возможно, что код, внешний по отношению к объекту foo_t
, будет хранить ссылки на его данные после окончания жизни объекта. Рассмотрим:
foo_t* f = new foo_t();
const int& x2 = f->x;
delete f;
std::cout << x2; // Undefined behavior; x2 refers into a foo_t object that was deleted
Или, еще проще:
const int& x2 = foo_t().x;
std::cout << x2; // Undefined behvior; x2 refers into a foo_t object that no longer exists
Это не совсем реалистичные примеры, но это потенциальная проблема всякий раз, когда объект раскрывает или возвращает ссылку на свои данные (публичные или приватные). Конечно, с тем же успехом можно хранить ссылку на сам объект foo_t
за пределами его жизни, но это труднее пропустить или сделать случайно.
Не то чтобы это было аргументом против того, что вы делаете. На самом деле я уже использовал этот паттерн раньше (по другой причине) и не думаю, что в нем есть что-то изначально неправильное, кроме отсутствия инкапсуляции, что вы, похоже, признаете. Вышеупомянутая проблема - это просто то, о чем следует знать.
Одна проблема в том, что ваш класс больше не может быть скопирован или назначен, и поэтому его нельзя хранить в контейнерах C ++, таких как векторы. Другой - то, что опытные программисты на C ++, обслуживающие ваш код, посмотрят на него и воскликнут «WTF !!» очень громко, что никогда не бывает хорошо.
Вы также можете сделать что-то вроде этого, что работает для встроенных типов: (Извините, если этот фрагмент кода содержит ошибки, но вы поняли идею)
template <typename T, typename F>
class read_only{
typedef read_only<T, F> my_type;
friend F;
public:
operator T() const {return mVal;}
private:
my_type operator=(const T& val) {mVal = val; return *this;}
T mVal;
};
class MyClass {
public:
read_only <int, MyClass> mInt;
void MyFunc() {
mInt = 7; //Works
}
};
AnyFunction(){
MyClass myClass;
int x = myClass.mVal; // Works (okay it hasnt been initalized yet so you might get a warning =)
myClass.mVal = 7; // Error
}
Ваш подход не является гибким. Когда у вас есть getter / setter
для каждой переменной, это означает, что вам не придется переписывать ваш set
метод, если вы добавите что-то в ваш класс.
Это не очень хорошо, потому что вы не можете иметь const
и non-const
геттеры (которые используются редко, но иногда могут быть полезны).
Вы не можете копировать ссылки, поэтому ваш класс становится некопируемым.
Кроме того, наличие инициализированных ссылок в вашем классе означает дополнительную память, и если мы говорим, например, о классе vertex (хотя я думаю, что это не должен быть класс) , это может стать катастрофой.
[Все, что следует далее, полностью субъективно]
Назначение геттеров и сеттеров, на мой взгляд, заключается не в простой модификации, а скорее в инкапсуляции последовательности действий, которые приводят к изменению значения или возвращают значение (которое мы рассматриваем как видимый результат).
Лично struct в случае вашего примера был бы более эффективен, поскольку он оборачивает данные POD и логически "структурирует" их.