C++ getter/setter 编码风格
-
09-09-2019 - |
题
我已经使用 C# 编程有一段时间了,现在我想温习我的 C++ 技能。
上课情况:
class Foo
{
const std::string& name_;
...
};
最好的方法是什么(我只想允许对 name_ 字段进行读取访问):
- 使用 getter 方法:
inline const std::string& name() const { return name_; }
- 公开该字段,因为它是一个常数
谢谢。
解决方案
将非常量字段公开往往是一个坏主意,因为这样就很难强制执行错误检查约束和/或在未来的值更改中添加副作用。
就您而言,您有一个 const 字段,因此上述问题不是问题。使其成为公共领域的主要缺点是您要锁定底层实现。例如,如果将来您想要将内部表示更改为 C 字符串或 Unicode 字符串或其他内容,那么您将破坏所有客户端代码。使用 getter,您可以转换为现有客户端的旧表示形式,同时通过新的 getter 向新用户提供更新的功能。
我仍然建议使用一种 getter 方法,就像您上面放置的那样。这将最大限度地提高您未来的灵活性。
其他提示
对于长寿命类来说,使用 getter 方法是更好的设计选择,因为它允许您在将来用更复杂的方法替换 getter 方法。尽管这对于 const 值来说似乎不太可能需要,但成本很低并且可能带来的好处很大。
顺便说一句,在 C++ 中,为成员提供 getter 和 setter 是一个特别好的主意 同名, ,因为将来您可以实际更改这对方法:
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 值同样有效,其中只需要 getter。
顺便说一句,在 C++ 中,有一个 const 引用成员有点奇怪。您必须在构造函数列表中分配它。谁拥有该对象的实际内存以及它的生命周期是多少?
至于风格,我同意其他人的观点,即你不想暴露你的隐私。:-) 我喜欢这种 setter/getter 模式
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 提供您自己的存储容器,或者只是确保唯一一次当您编写自定义 getter/setter 方法时,您会使用“GetRawData”。
尽管名称是不可变的,您可能仍然希望选择计算它而不是将其存储在字段中。(我意识到这对于“名称”来说不太可能,但让我们针对一般情况。)因此,即使是常量字段也最好包装在 getters 内:
class Foo {
public:
const std::string& getName() const {return name_;}
private:
const std::string& name_;
};
请注意,如果您要更改 getName()
要返回计算值,它无法返回 const ref。没关系,因为它不需要对调用者进行任何更改(模数重新编译)。
避免使用公共变量,但本质上是 C 风格结构的类除外。这并不是一个好的做法。
一旦定义了类接口,您可能永远无法更改它(除了添加它之外),因为人们将在它的基础上构建并依赖它。将变量设为公共意味着您需要拥有该变量,并且需要确保它具有用户所需的内容。
现在,如果您使用 getter,您就承诺提供一些信息,这些信息当前保存在该变量中。如果情况发生变化,并且您不想一直维护该变量,则可以更改访问权限。如果需求发生变化(我见过一些非常奇怪的需求变化),并且您主要需要此变量中的名称,但有时需要该变量中的名称,您只需更改 getter 即可。如果你将变量公开,你就会被困住。
这种情况不会总是发生,但我发现仅仅编写一个快速获取器比分析情况以查看我是否会后悔将变量公开(并且有以后出错的风险)要容易得多。
将成员变量设为私有是一个值得养成的好习惯。任何有代码标准的商店都可能会禁止偶尔公开成员变量,而任何有代码审查的商店可能会因此批评你。
每当写作的难易程度确实不重要时,就养成更安全的习惯。
从多个 C++ 源收集想法,并将其放入一个很好的、仍然相当简单的 C++ getter/setter 示例中:
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
附:FO 伊维特 <3
来自设计模式理论;“封装变化的东西”。通过定义“吸气剂”,可以很好地遵守上述原则。因此,如果成员的实现表示将来发生变化,则可以在从“getter”返回之前对该成员进行“按摩”;意味着在进行“getter”调用的客户端没有代码重构。
问候,