类设计问题 - 如何提供对类成员容器的只读访问

时间:2010-11-20 13:02:46

标签: c++

在我的日常工作中,我经常发现自己正在编写类似于这个简化示例的类:

class CGarage
{
public:
    CGarage();
    ~CGarage();
    typedef std::vector<Car> CarCollection;

private:
    CarCollection m_Cars;
};

我希望CGarage的用户只能访问CarCollection。为了实现这一目标,这些是一些不太令人满意的常见解决方案:

解决方案1 ​​

class CGarage
{
    Car GetCar(CarCollection::size_type index) const;
    CarCollection::size_type CarCount() const;
};

主要缺点:

  • 缺乏迭代器,我不能在汽车上使用STL算法(例如for_each(...))

解决方案2

class CGarage
{
    CarCollection::const_iterator CarBegin() const;
    CarCollection::const_iterator CarEnd() const;
    CarCollection::size_type CarCount() const;
};

主要缺点:

  • 如果您需要支持其他迭代器类型(它,reverse_it),那么很多样板代码。

解决方案3

class CGarage
{
    const CarCollection GetCars() const;
};

主要缺点:

  • 按值返回时复制CarCollection的成本
  • 类用户已知的实现细节(例如,在不更改破坏用户代码的情况下无法更改为std :: list)

解决方案4

class CGarage
{
    const CarCollection& GetCars() const;
};

主要缺点:

  • CarCollection引用的生命周期与CGarage的生命周期绑定
  • 用户已知的实现细节

问题

您如何提供对CarCollection的只读访问权限?

如果CarCollection是一个带Car指针的向量,那么你的解决方案会改变吗?

如果您允许对集合进行读写访问,是否可以公开该集合?

感谢您的任何建议

6 个答案:

答案 0 :(得分:5)

  

您如何提供对CarCollection的只读访问权限?

我不明白解决方案4有什么问题。对于CGarage的用户来说,显而易见的是,对其汽车收藏的参考与车库的使用寿命有关。如果他们需要汽车收藏品比车库更长,那么他们总是可以随意拿一份副本。

或者,让CGarage为汽车收藏品保留一个shared_ptr并返回,但我不会推荐它。

  

如果CarCollection是一个带Car指针的向量,那么你的解决方案会改变吗?

对于拥有对象(即引用类型)的集合,最好使用不同的容器。 std::容器都是为值类型设计的,并且不能很好地处理引用类型(尤其是constness)。对于这些,请使用Boost的ptr_vector

  

如果您允许对集合进行读写访问,是否可以公开该集合?

取决于您的具体情况。集合的语义可能会改变吗?如果没有,那么您可以安全地将其公开(例如std::pair)。我不建议您针对特定领域的问题执行此操作。

答案 1 :(得分:1)

这样声明就不够了吗?

const CarCollection& Cars() { return m_Cars; }

然后

 CarCollection::const_iterator it = garage.Cars().begin();

应该可以工作但是

  CarCollection::iterator it = garage.Cars().begin();

会给出错误。

答案 2 :(得分:1)

我会选择解决方案4 。关于指针容器的常量问题,你可以使用正确传播constness的boost::ptr_vector(除其他外)。

答案 3 :(得分:1)

取决于。

  1. 如果班级的客户需要矢量汽车,例如。他们假设汽车因任何原因连续存储在内存中(虽然我没有看到其他原因),然后你应该提供一个成员函数,返回一个向量的常量引用。不要为传递价值和与对象创建相关的固有异常安全问题而烦恼。如果向量必须比Garage对象更长,则客户端将进行复制。

  2. 现在,在任何其他情况下,最小耦合原则表明我们返回一对迭代器。毕竟,一对迭代器是集合的通用表达式。如果类的客户端需要复杂性假设,那么返回正确类型的迭代器(并记录它)。

  3. 为方便起见,仅在有意义时,您可以重载operator[]。车库是汽车的集合。如果车库内的停车位已经编号,并且您想要通过插槽访问汽车,那么这可能是一个很好的解决方案。

  4. 尽管如此,你说C ++迭代器的设计方式迫使你编写大量的样板代码是正确的。然后,当您第一次编写类时,将const引用返回给向量,然后重构。

答案 4 :(得分:0)

我可以回答一个问题:

  

如果您允许对集合进行读写访问,是否可以公开该集合?

不一定。如果您需要更改实现,或者需要在更改变量时运行一段代码,该怎么办?即使你不这样做,你可能需要将来,并且从public var到set / get的更改可能会破坏很多代码。

答案 5 :(得分:0)

选项4,但返回对包含显式转换运算符到标准集合的抽象基类的引用。这至少会削减实施细节的曝光程度。

为了进一步减少对实现细节的依赖,将包含集合的类转换为模板,参数化集合类型。