封装能走多远?
How far to go in encapsulation?
对于 C++ 中应该扩展多深的封装是否存在普遍共识?我正在查看的具体示例是您是否应该限制对 class 中向量的访问。 vector应该用getVector()
传递,还是应该限制在getVectorItem(int i)
和addVectorItem(int x)
?
public:
vector<int>* getVector() const;
其中 getVector returns 向量的指针
或
public:
int getVectorItem(int i) const;
其中 getVectorItem(i)
returns 无论 _vector[i]
是什么。
你明白我的意思了吗?
每种不同的情况都需要不同程度的封装,因此不可能对一般问题给出一般答案。
我可能建议你只是让包含 class 的向量直接可迭代,比如:
class Foo
{
private:
vector<int> data;
public:
typedef typename decltype(data)::iterator iterator;
typedef typename decltype(data)::const_iterator const_iterator;
iterator begin() { return data.begin(); }
iterator end() { return data.end(); }
const_iterator begin() const ...
}
通过这种方式,您不会公开 Foo
的内部结构,但您将能够在标准 STL 算法等中使用它。当然,这只有在你有一个包含数据的数据结构时才有效,除非你注意使用不同的命名方案来检索迭代器。
根据用例,最好有一种方法可以在不暴露底层数据的情况下访问和修改成员。在您的具体示例中,这意味着第二个选项是要走的路。
public:
int getItem(int index) const;
void setItem(int index, int item);
如果底层数据结构发生变化,您不必修改用户访问包装器的方式。如果您要 return 处理更复杂的数据,那么您可以 return const 引用来明确用户应该如何访问数据。
public:
ComplexClass const& getItem(std::string const& key) const;
void setItem(std::string const& key, ComplexClass const& item);
和以前一样,通过 public getters/setters 可以防止用户对 class 拥有的内部数据进行畸形处理,并允许您实施任何类型的保护。
例如,如果用户 return编辑了一个数组,如何阻止他们编辑不正确的元素?
class Foo
{
public:
std::vector<int> & getContainer() { return container; }
private:
std::vector<int> container;
};
Foo foo;
std::vector<int> vector = foo.getContainer();
for(int i = 0; i < 10; ++i)
vector.push_back(i);
vector[99999] = 0; // The program will crash here
使用自定义包装器虽然我们可以防止崩溃
class Foo
{
public:
Foo() : container(10) {}
int getItem(int index) const
{
if(index >= container.size() || index < 0)
return -1; // error case
else
return container[index];
}
void setItem(int index, int item)
{
if(index < container.size() && index > 0)
container[index] = item;
}
private:
std::vector<int> container;
};
Foo foo;
for(int i = 0; i < 10; ++i)
foo.setItem(i, i);
foo.setItem(99999, 0); // Nothing happens
您甚至可以添加迭代器运算符重载,使访问看起来与访问向量完全一样。但是,如果您要围绕容器创建包装器,请确保实现可迭代接口。
两者不一样。例如,前者允许调用者调整向量的大小,而后者则不允许。所以这不是风格问题,而是实际的设计选择,只有您自己知道 class 您希望外界能够改变多少。
对于 C++ 中应该扩展多深的封装是否存在普遍共识?我正在查看的具体示例是您是否应该限制对 class 中向量的访问。 vector应该用getVector()
传递,还是应该限制在getVectorItem(int i)
和addVectorItem(int x)
?
public:
vector<int>* getVector() const;
其中 getVector returns 向量的指针
或
public:
int getVectorItem(int i) const;
其中 getVectorItem(i)
returns 无论 _vector[i]
是什么。
你明白我的意思了吗?
每种不同的情况都需要不同程度的封装,因此不可能对一般问题给出一般答案。
我可能建议你只是让包含 class 的向量直接可迭代,比如:
class Foo
{
private:
vector<int> data;
public:
typedef typename decltype(data)::iterator iterator;
typedef typename decltype(data)::const_iterator const_iterator;
iterator begin() { return data.begin(); }
iterator end() { return data.end(); }
const_iterator begin() const ...
}
通过这种方式,您不会公开 Foo
的内部结构,但您将能够在标准 STL 算法等中使用它。当然,这只有在你有一个包含数据的数据结构时才有效,除非你注意使用不同的命名方案来检索迭代器。
根据用例,最好有一种方法可以在不暴露底层数据的情况下访问和修改成员。在您的具体示例中,这意味着第二个选项是要走的路。
public:
int getItem(int index) const;
void setItem(int index, int item);
如果底层数据结构发生变化,您不必修改用户访问包装器的方式。如果您要 return 处理更复杂的数据,那么您可以 return const 引用来明确用户应该如何访问数据。
public:
ComplexClass const& getItem(std::string const& key) const;
void setItem(std::string const& key, ComplexClass const& item);
和以前一样,通过 public getters/setters 可以防止用户对 class 拥有的内部数据进行畸形处理,并允许您实施任何类型的保护。
例如,如果用户 return编辑了一个数组,如何阻止他们编辑不正确的元素?
class Foo
{
public:
std::vector<int> & getContainer() { return container; }
private:
std::vector<int> container;
};
Foo foo;
std::vector<int> vector = foo.getContainer();
for(int i = 0; i < 10; ++i)
vector.push_back(i);
vector[99999] = 0; // The program will crash here
使用自定义包装器虽然我们可以防止崩溃
class Foo
{
public:
Foo() : container(10) {}
int getItem(int index) const
{
if(index >= container.size() || index < 0)
return -1; // error case
else
return container[index];
}
void setItem(int index, int item)
{
if(index < container.size() && index > 0)
container[index] = item;
}
private:
std::vector<int> container;
};
Foo foo;
for(int i = 0; i < 10; ++i)
foo.setItem(i, i);
foo.setItem(99999, 0); // Nothing happens
您甚至可以添加迭代器运算符重载,使访问看起来与访问向量完全一样。但是,如果您要围绕容器创建包装器,请确保实现可迭代接口。
两者不一样。例如,前者允许调用者调整向量的大小,而后者则不允许。所以这不是风格问题,而是实际的设计选择,只有您自己知道 class 您希望外界能够改变多少。