【问题标题】:Standard containers encapsulation and range-based for loops标准容器封装和基于范围的 for 循环
【发布时间】:2017-11-27 20:57:41
【问题描述】:

我正在设计一个有两个标准向量作为成员的类。我希望能够在向量元素上使用基于范围的 for 循环,我想出了这个解决方案

#include <iostream>
#include <vector>

using namespace std;

class MyClass {
public:
  void addValue1(int val){data1_.push_back(val);}
  void addValue2(int val){data2_.push_back(val);}
  vector<int> const & data1() const {return data1_;}
  vector<int> const & data2() const {return data2_;}
  // ...
private:
  vector<int> data1_;
  vector<int> data2_;
  // ...
};

void print1(MyClass const & mc) {
  for (auto val : mc.data1()){
    cout << val << endl;
  }
}

void print2(MyClass const & mc) {
  for (auto val : mc.data2()){
    cout << val << endl;
  }
}

int main(){
  MyClass mc;
  mc.addValue1(1);
  mc.addValue1(2);
  mc.addValue1(3);
  print1(mc);
}

显然,定义 begin()end() 函数的替代方法没有意义,因为我有两个不同的向量。

我想问以下问题:

  • 建议的解决方案的一个缺点是不能更改两个向量的内容(由于const 限定符)。如果我需要修改矢量元素,我该如何修改代码? 编辑:修改应保留封装

  • 考虑到数据封装,您认为返回两个向量的 (const) 引用是不好的做法吗?

【问题讨论】:

  • “修改向量的内容”根据定义是取消封装它们。这就是封装。因此,简单的答案变成:struct MyClass { vector&lt;int&gt; data1; vector&lt;int&gt; data2; }
  • @Caleth :在我的问题中,我写道我希望能够更改向量元素的值,但我不想提供添加和删除元素的能力。据我了解,封装要求只能对数据执行某些安全操作(即我的应用程序写入和读取元素值),而其他操作是隐藏的(即我的应用程序添加和删除数据)。
  • 哦,在这种情况下,您肯定想要@Yakk 的解决方案。在 MyClass 的公共成员中不提及 vector 是“更加封装”

标签: c++ c++11 containers c++14 encapsulation


【解决方案1】:

使用 gsl::span&lt;int&gt;gsl::span&lt;const int&gt; 之类的东西。

这是一个最小的:

template<class T>
struct span {
  T* b = 0; T* e = 0;
  T* begin() const { return b; }
  T* end() const { return e; }
  span( T* s, T* f ):b(s),e(f) {}
  span( T* s, std::size_t len ):span(s, s+len) {}
  template<std::size_t N>
  span( T(&arr)[N] ):span(arr, N) {}
  // todo: ctor from containers with .data() and .size()

  // useful helpers:
  std::size_t size() const { return end()-begin(); }
  bool empty() const { return size()==0; }
  T& operator[](std::size_t i) const { return begin()[i]; }
  T& front() const { return *begin(); }
  T& back() const { return *(std::prev(end())); }

  // I like explicit defaults of these:
  span() = default;
  span(span const&) = default;
  span& operator=(span const&) = default;
  ~span() = default;
};

现在你可以写了:

span<int const> data1() const {return {data1_.data(), data1_.size()};}
span<int const> data2() const {data2_.data(), data2_.size()};}
span<int> data1() {return {data1_.data(), data1_.size()};}
span<int> data2() {data2_.data(), data2_.size()};}

【讨论】:

    【解决方案2】:

    建议的解决方案的一个缺点是两个向量的内容不能更改(由于 const 限定符)。如果我需要修改矢量元素,我该如何修改代码?

    首先,您应该添加一个data1() 和一个data2() 非常量版本,它们返回对data1_data2_ 成员的引用

    vector<int> const & data1() const {return data1_;}
    vector<int> const & data2() const {return data2_;}
    
    vector<int> & data1() {return data1_;}
    vector<int> & data2() {return data2_;}
    

    第二:如果你想修改print1()中的元素(例如)你必须接收mc作为非const引用

    // ..........vvvvvvvvv   no more const
    void print1 (MyClass & mc) {
    

    这样你就可以更改mc

    第三:在基于范围的循环中,您必须将val 定义为引用,以便您可以修改它,同时修改向量内的引用值

    // ........V   by reference
    for ( auto & val : mc.data1() ) {
       ++val ;  // this modify the value in the vector inside mc
       cout << val << endl;
    }
    

    考虑到数据封装,您认为返回对两个向量的 (const) 引用是不好的做法吗?

    恕我直言:如果引用是const,则根本不是:这是一个好习惯,因为无需复制即可安全使用该成员。

    如果引用不是const,我认为将成员声明为public 并没有太大区别。

    【讨论】:

    • 也许我的问题不是很清楚,但我想在代码修改后也有良好的封装性。我编辑了我的问题以明确这一点。感谢您回答我的第二个问题。
    猜你喜欢
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-06
    • 2014-01-12
    • 2013-01-04
    相关资源
    最近更新 更多