C++ геттеры / сеттеры стиль кодирования
Я некоторое время программировал на C#, и теперь я хочу освежить свои навыки C++.
имея класс:
class Foo
{
const std::string& name_;
...
};
что было бы лучшим подходом (я только хочу разрешить доступ для чтения к полю name_):
- используйте метод геттера:
inline const std::string& name() const { return name_; }
- сделать поле публичным, так как это константа
спасибо.
7 ответов:
Это, как правило, плохая идея сделать неконстантные поля общедоступными, потому что тогда становится трудно заставить ограничения проверки ошибок и/или добавить побочные эффекты к изменениям значений в будущем.
в вашем случае у вас есть поле const, поэтому вышеуказанные проблемы не являются проблемой. Основным недостатком создания публичного поля является то, что вы блокируете базовую реализацию. Например, если в будущем вы хотите изменить внутреннее представление на C-строку или Unicode строка или что-то еще, тогда вы сломаете весь код клиента. С помощью gettor вы можете конвертировать в устаревшее представление для существующих клиентов, предоставляя новые функции новым пользователям через новый gettor.
Я бы все равно предложил иметь метод геттера, подобный тому, который вы разместили выше. Это увеличит вашу гибкость.
использование метода геттера является лучшим выбором дизайна для долгоживущего класса, поскольку он позволяет заменить метод геттера на что-то более сложное в будущем. Хотя это кажется менее вероятным для постоянной стоимости, Стоимость низкая, а возможные выгоды велики.
кстати, в C++, это особенно хорошая идея, чтобы дать как геттер и сеттер для то же имя, так как в будущем вы можете фактически изменить пара методов:
class Foo { public: std::string const& name() const; // Getter void name(std::string const& newName); // Setter ... };в одну открытую переменную-член, которая определяет
operator()()для каждого:// This class encapsulates a fancier type of name class fancy_name { public: // Getter std::string const& operator()() const { return _compute_fancy_name(); // Does some internal work } // Setter void operator()(std::string const& newName) { _set_fancy_name(newName); // Does some internal work } ... }; class Foo { public: fancy_name name; ... };клиентский код, конечно, нужно будет перекомпилировать, но никаких изменений синтаксиса не требуется! Очевидно, что это преобразование работает так же хорошо для значений const, в которых требуется только геттер.
в стороне, в C++, несколько странно иметь ссылочный элемент const. Вы должны назначить его в списке конструкторов. Кто на самом деле владеет памятью этого объекта и какова его продолжительность жизни?
что касается стиля, я согласен с другими, что вы не хотите выставлять свои интимные места. :- ) Мне нравится этот шаблон для сеттеров / геттеров
class Foo { public: const string& FirstName() const; Foo& FirstName(const string& newFirstName); const string& LastName() const; Foo& LastName(const string& newLastName); const string& Title() const; Foo& Title(const string& newTitle); };таким образом, вы можете сделать что-то вроде:
Foo f; f.FirstName("Jim").LastName("Bob").Title("Programmer");
Я думаю, что подход C++11 будет больше похож на это сейчас.
#include <string> #include <iostream> #include <functional> template<typename T> class LambdaSetter { public: LambdaSetter() : getter([&]() -> T { return m_value; }), setter([&](T value) { m_value = value; }), m_value() {} T operator()() { return getter(); } void operator()(T value) { setter(value); } LambdaSetter operator=(T rhs) { setter(rhs); return *this; } T operator=(LambdaSetter rhs) { return rhs.getter(); } operator T() { return getter(); } void SetGetter(std::function<T()> func) { getter = func; } void SetSetter(std::function<void(T)> func) { setter = func; } T& GetRawData() { return m_value; } private: T m_value; std::function<const T()> getter; std::function<void(T)> setter; template <typename TT> friend std::ostream & operator<<(std::ostream &os, const LambdaSetter<TT>& p); template <typename TT> friend std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p); }; template <typename T> std::ostream & operator<<(std::ostream &os, const LambdaSetter<T>& p) { os << p.getter(); return os; } template <typename TT> std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p) { TT value; is >> value; p.setter(value); return is; } class foo { public: foo() { myString.SetGetter([&]() -> std::string { myString.GetRawData() = "Hello"; return myString.GetRawData(); }); myString2.SetSetter([&](std::string value) -> void { myString2.GetRawData() = (value + "!"); }); } LambdaSetter<std::string> myString; LambdaSetter<std::string> myString2; }; int _tmain(int argc, _TCHAR* argv[]) { foo f; std::string hi = f.myString; f.myString2 = "world"; std::cout << hi << " " << f.myString2 << std::endl; std::cin >> f.myString2; std::cout << hi << " " << f.myString2 << std::endl; return 0; }Я тестировал это в Visual Studio 2013. К сожалению, для того, чтобы использовать хранилище внутри LambdaSetter мне нужно, чтобы обеспечить "GetRawData" общественного доступа, который может привести к переломам инкапсуляции, но вы можете либо оставить его и предоставить собственный контейнер для хранения на T или просто убедитесь, что единственный раз, когда вы используете "GetRawData" при написании пользовательского геттер/сеттер метод.
несмотря на то, что имя является неизменяемым, вы все равно можете иметь возможность вычислять его, а не хранить в поле. (Я понимаю, что это маловероятно для" имени", но давайте нацелимся на общий случай.) По этой причине даже постоянные поля лучше всего обернуты внутри геттеров:
class Foo { public: const std::string& getName() const {return name_;} private: const std::string& name_; };обратите внимание, что если вы должны были изменить
getName()чтобы вернуть вычисленное значение, он не может вернуть const ref. Это нормально, потому что это не потребует никаких изменений для вызывающих абонентов (по модулю перекомпиляция.)
избегайте открытых переменных, за исключением классов, которые по существу являются структурами C-стиля. Это просто не очень хорошая практика, чтобы войти.
после того, как вы определили интерфейс класса, вы никогда не сможете его изменить (кроме добавления к нему), потому что люди будут строить на нем и полагаться на него. Сделать переменную общедоступной означает, что вам нужно иметь эту переменную, и вам нужно убедиться, что она имеет то, что нужно пользователю.
теперь, если вы используете геттер, вы обещаете поставлять некоторая информация, которая в настоящее время хранится в этой переменной. Если ситуация меняется, и вы не хотите поддерживать эту переменную все время, вы можете изменить доступ. Если требования меняются (и я видел некоторые довольно странные изменения требований), и вам в основном нужно имя, которое находится в этой переменной, но иногда в этой переменной, вы можете просто изменить геттер. Если бы вы сделали переменную общедоступной, вы бы застряли с ней.
Это не всегда будет происходить, но я нахожу это гораздо проще просто написать быстрый геттер, чем анализировать ситуацию, чтобы увидеть, сожалею ли я о том, что сделал переменную публичной (и рискуете ошибиться позже).
сделать переменные-члены частными-это хорошая привычка. Любой магазин, который имеет стандарты кода, вероятно, запретит публиковать случайные переменные-члены, и любой магазин с обзорами кода, вероятно, будет критиковать вас за это.
всякий раз, когда это действительно не имеет значения для удобства написания, попасть в безопасный привычка.
из теории шаблонов проектирования; "инкапсулируйте то, что изменяется". При определении "геттера" существует хорошее соблюдение вышеуказанного принципа. Таким образом, если реализация-представление члена изменяется в будущем, член может быть "массирован" перед возвращением из "геттера"; подразумевая отсутствие рефакторинга кода на стороне клиента, где выполняется вызов "геттера".
с уважением,
Comments