Стиль кодирования/компоновщика С++
Я программировал на С# некоторое время, и теперь я хочу освежить свои навыки на С++.
Наличие класса:
class Foo
{
const std::string& name_;
...
};
Какой лучший подход (я хочу разрешить доступ только для чтения в поле name_):
- используйте метод getter:
inline const std::string& name() const { return name_; }
- сделать поле общедоступным, так как это константа
Спасибо.
Ответы
Ответ 1
Как правило, плохая идея - сделать неконстантные поля общедоступными, потому что тогда становится трудно принудительно устанавливать ограничения проверки ошибок и/или добавлять побочные эффекты к изменениям значений в будущем.
В вашем случае у вас есть поле const, поэтому вышеуказанные проблемы не являются проблемой. Основным недостатком сделать его публичным полем является то, что вы блокируете базовую реализацию. Например, если в будущем вы захотите изменить внутреннее представление на C-строку или строку Unicode или что-то еще, то вы нарушите весь клиентский код. С помощью геттера вы можете преобразовать его в устаревшее представление для существующих клиентов, одновременно предоставляя новые функции новым пользователям через новый геттер.
Я бы все же предложил использовать метод получения, подобный тому, который вы поместили выше. Это увеличит вашу будущую гибкость.
Ответ 2
Использование метода геттера - лучший выбор дизайна для долгоживущего класса, поскольку он позволяет вам заменить метод геттера чем-то более сложным в будущем. Хотя это кажется менее вероятным для значения const, стоимость низкая и возможные выгоды большие.
Как в стороне, на С++, особенно хорошая идея дать и getter и setter для одного и того же имени, так как в будущем вы можете фактически изменить пару методов:
class Foo {
public:
std::string const& name() const; // Getter
void name(std::string const& newName); // Setter
...
};
В одну переменную public member, которая определяет 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;
...
};
Клиентский код нужно будет перекомпилировать, конечно, но никаких изменений синтаксиса не требуется! Очевидно, что это преобразование работает так же хорошо для константных значений, в которых требуется только геттер.
Ответ 3
Как в стороне, в С++, несколько странно иметь элемент ссылки 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");
Ответ 4
Я думаю, что теперь подход С++ 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", когда вы пишете пользовательский метод getter/setter.
Ответ 5
Несмотря на то, что имя является неизменным, вы все равно можете иметь возможность его вычислить, а не хранить его в поле. (Я понимаю, что это маловероятно для "имени", но пусть нацеливается на общий случай.) По этой причине даже константные поля лучше всего обернуты внутри геттеров:
class Foo {
public:
const std::string& getName() const {return name_;}
private:
const std::string& name_;
};
Обратите внимание, что если вы должны были изменить getName()
, чтобы вернуть вычисленное значение, он не смог бы возвратить const ref. Это нормально, потому что это не потребует никаких изменений для вызывающих (по модулю перекомпиляции.)
Ответ 6
Избегайте общедоступных переменных, за исключением классов, которые по существу являются структурами C-стиля. Это просто не очень хорошая практика.
Как только вы определили интерфейс класса, вы никогда не сможете его изменить (кроме добавления к нему), потому что люди будут использовать его и полагаться на него. Создание переменной public означает, что вам нужно иметь эту переменную, и вам нужно убедиться, что она имеет то, что нужно пользователю.
Теперь, если вы используете геттер, вы обещаете предоставить некоторую информацию, которая в настоящее время хранится в этой переменной. Если ситуация изменится, и вы не хотите постоянно поддерживать эту переменную, вы можете изменить ее. Если требования меняются (и я видел некоторые довольно странные изменения требований), и вам в основном нужно имя, которое в этой переменной, но иногда одно в этой переменной, вы можете просто изменить getter. Если вы сделали переменную общедоступной, вы бы застряли с ней.
Это не всегда произойдет, но мне гораздо проще просто написать быстрый геттер, чем проанализировать ситуацию, чтобы увидеть, не пожалел ли я сделать переменную общедоступной (и позже риск ошибиться).
Использование переменных элемента private - хорошая привычка. Любой магазин, который имеет стандарты кода, вероятно, будет запрещать публиковать переменную-членскую переменную, и любой магазин с обзорами кода может критиковать вас за это.
Всякий раз, когда это действительно не важно для удобства написания, входите в более безопасную привычку.
Ответ 7
Собрал идеи из нескольких источников 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
PS: FO Yvette <3
Ответ 8
Из теории шаблонов проектирования; "инкапсулировать то, что меняется". Определяя "геттер", существует хорошая приверженность указанному выше принципу. Итак, если реализация-представление участника изменится в будущем, член может быть "массирован" перед возвратом из "getter"; подразумевая отсутствие рефакторинга кода на стороне клиента, где сделан запрос "getter".
Привет,