对抽象接口的所有权转移的指针向量的const-correct访问器

Tor*_*örn 10 c++ api-design c++14 c++17

我正在设计一个从头开始的库,并希望尽可能地获得公共API .我希望编译器因误用而对我大喊大叫.因此,我对自己施加了以下规则:

  1. 整个库中的true(即深度和完整)const正确性

    声明不会发生变化的所有事物(局部变量,成员变量,成员函数)const.该constness应该传播到所有嵌套的成员和类型.

  2. 明确和富有表现力的所有权

    根据C++核心指南,我将其定义为(iff在数学意义上的if和only if):

    1. 函数参数是unique_ptr<T>T&& iff函数正在消耗它(即取得所有权)
    2. 函数参数是shared_ptr<const T>T const& iff函数只读它
    3. 函数参数是shared_ptr<T>或者T& 如果函数正在修改它而不取得所有权
    4. 返回值是unique_ptr<T>T iff函数将所有权转移给调用者
    5. 返回值是shared_ptr<const T>T const& iff调用者只应读取它(虽然调用者可以构造它的副本 - 给定T是可复制的)
    6. 没有功能应该返回shared_ptr<T>,T&T*(因为它会允许无法控制的副作用,我试图通过设计避免)
  3. 隐藏的实施细节

    现在,我将使用抽象接口与工厂将实现作为一个返回unique_ptr<Interface>.虽然,我愿意接受解决我下面描述的问题的替代模式.

我不关心虚拟表查找,并希望通过各种方式避免动态转换(我将它们视为代码气味).


现在,给出了两个班AB,其中B拥有可变数量的A秒.我们也有 - B实现BImpl(A这里可能没有使用的实现):

class A
{};

class B {
 public:
  virtual ~B() = default;
  virtual void addA(std::unique_ptr<A> aObj) = 0;
  virtual ??? aObjs() const = 0;
};

class BImpl : public B {
 public:
  virtual ~BImpl() = default;
  void addA(std::unique_ptr<A> aObj) override;
  ??? aObjs() const override;

 private:
  std::vector<unique_ptr<A>> aObjs_;
};
Run Code Online (Sandbox Code Playgroud)

我坚持使用Bs getter 的返回值到As:的向量aObjs().
它应该提供As 列表作为只读值而不转移所有权(规则2.5.上面带有const正确性)并且仍然为调用者提供对所有As的轻松访问,例如用于基于范围的for或标准算法,例如std::find.

我想出了以下选项???:

  1. std::vector<std::shared_ptr<const A>> const&

    每次调用时我都必须构造一个新的向量aObjs()(我可以将其缓存BImpl).这感觉不仅低效且不必要地复杂,而且似乎也非常不理想.

  2. 替换aObjs()为一对函数(aObjsBegin()aObjsEnd())转发常量迭代器BImpl::aObjs_.

    等待.我需要做unique_ptr<A>::const_iterator一个unique_ptr<const A>::const_iterator让我心爱的const正确.再次讨厌的演员或中间对象.并且用户无法在基于范围的情况下轻松使用它for.

我错过了什么明显的解决方案?


编辑:

  • B应该总是能够修改A其持有S,从而宣告aObjs_vector<std::unique_ptr<const A>>不是一种选择.

  • 让我们B遵循迭代器概念迭代As,既不是一个选项,因为B它将保存一个Cs 列表和一个特定的D(或没有).

Jar*_*d42 3

使用range-v3,你可以这样做

template <typename T>
using const_view_t = decltype(std::declval<const std::vector<std::unique_ptr<T>>&>() 
                        | ranges::view::transform(&std::unique_ptr<T>::get)
                        | ranges::view::indirect);

class B
{
public:
    virtual ~B() = default;
    virtual void addA(std::unique_ptr<A> a) = 0;    
    virtual const_view_t<A> getAs() const = 0;
};

class D : public B
{
public:
    void addA(std::unique_ptr<A> a) override { v.emplace_back(std::move(a)); }
    const_view_t<A> getAs() const override {
        return v | ranges::view::transform(&std::unique_ptr<A>::get)
                 | ranges::view::indirect;
    }

private:
    std::vector<std::unique_ptr<A>> v;
};
Run Code Online (Sandbox Code Playgroud)

进而

for (const A& a : d.getAs()) {
    std::cout << a.n << std::endl;   
}
Run Code Online (Sandbox Code Playgroud)

演示