让我们分别处理这两点。
1. 类不变量
在编程中,当你有不变量时,推理会变得更容易。例如,您可能已经听说过循环不变量:
for (size_t i = 0; i < vec.size(); ++i) {
// something that does not alter i
}
在这个循环中,0 <= i < vec.size()
是一个不变量,它保证它vec[i]
始终是一个有效的表达式。
类也可以有不变量,例如,如果您考虑std::string
其size()
方法返回其缓冲区中的字符数。总是。
现在,假设您编写自己的字符串类:
// Invariant: size represents the number of characters in data.
struct String {
size_t size;
char* data;
};
记录你希望的不变量是很好的,但我可以完美地做到:
void reset(String& str) {
delete str.data;
str.data = 0;
}
而忘记重置str.size
,从而违反了不变量。
但是,如果你把班级成员藏起来:
// Invariant: size() returns the number of characters accessible via data()
class String {
public:
size_t size() const { return _size; }
char const* data() const { return _data; }
// methods which maintain the invariant
private:
size_t _size;
char* _data;
};
现在只有你可以违反不变量。因此,在出现错误的情况下,您需要审核的代码更少。
2.实施改变绝缘
背后的想法是,您应该能够在不调整类用户的情况下切换信息的内部表示。例如:
class Employee {
public:
std::string const& name() const { return _name; } // Bad
private:
std::string _name;
}; // class Employee
现在,如果我意识到这std::string
不是名称的适当表示(例如,我需要宽字符):
class Employee {
public:
std::string const& name() const { return _name; } // error!
private:
std::basic_string<char32_t> _name;
}; // class Employee
我被困住了。我不能再返回了std::string const&
(我没有内部了std::string
)。我可以更改返回name()
以制作副本:
std::string Employee::name() const { return encodeUtf8(_name); }
不幸的是,它仍然可能会破坏客户:
std::string const& name(Employee const& e) {
std::string const& n = e.name(); // Bind temporary variable to const&
return n; // Returns reference to local variable!!
}
而如果从一开始就Employee
设计好了,我们可以毫无问题地进行更改。std::string name() const
注意:在实际使用中,您必须在考虑绝缘的情况下制作外部 API,但内部 API 可能会完美地暴露数据表示......以在进行更改时在您的盘子上进行更多更改为代价。