封装能走多远?

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 您希望外界能够改变多少。