gpt4 book ai didi

c++ - 在抽象接口(interface)中不转移所有权的指针 vector 的常量正确访问器

转载 作者:可可西里 更新时间:2023-11-01 16:01:09 25 4
gpt4 key购买 nike

我正在从头开始设计一个库,并希望获得尽可能良好的公共(public) API。我希望编译器因滥用而对我大喊大叫。因此,我给自己制定了以下规则:

  1. 整个库的真实(即深入和完整)const 正确性

    所有东西(局部变量、成员变量、成员函数),预计不会改变的都被声明为const .该常量性应传播到所有嵌套成员和类型。

  2. 明确和表达所有权

    根据 C++ 核心指南,我将其定义为(iff当且仅当 的数学意义上):

    1. 函数参数是unique_ptr<T>T&& iff 函数正在使用它(即取得所有权)
    2. 函数参数是shared_ptr<const T>T const& iff函数只读取它
    3. 函数参数是shared_ptr<T>T& iff 函数正在修改它而不取得所有权
    4. 返回值为unique_ptr<T>T iff函数将所有权转移给调用者
    5. 返回值为shared_ptr<const T>T const& iff 调用者应该只读取它(尽管调用者可以构建它的拷贝 - 给定 T 是可复制的)
    6. 没有函数应该返回 shared_ptr<T> , T&T* (因为它会导致无法控制的副作用,我试图通过设计避免)
  3. 隐藏的实现细节

    现在我将使用抽象接口(interface),工厂将实现作为 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_;
};

我被 B 的返回值卡住了A vector 的 setter/getter 小号:aObjs() .
它应该提供 A 的列表s 作为只读值,不转移所有权(规则 2.5。上面的 const 正确性)并且仍然为调用者提供对所有 A 的轻松访问。 s,例如用于基于范围的 for或标准算法,例如 std::find .

我为那些 ??? 想出了以下选项:

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

    我每次调用 aObjs() 时都必须构建一个新 vector (我可以将其缓存在 BImpl 中)。这感觉不仅效率低下且不必要地复杂,而且看起来也不是最理想的。

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

    等等。我需要做那个 unique_ptr<A>::const_iterator一个unique_ptr<const A>::const_iterator得到我心爱的 const 正确性。再次令人讨厌的类型转换或中间对象。并且用户无法在基于范围的 for 中轻松使用它.

我缺少什么明显的解决方案?


编辑:

  • B应该总是能够修改 A它持有,因此宣布aObjs_作为vector<std::unique_ptr<const A>>不是一个选项。

  • B坚持迭代器概念迭代A s, 既不是 B 的选项将持有 C 的列表s 和特定的 D (或没有)。

最佳答案

您可以返回一个 vector 包装器,而不是直接返回 vector ,它允许您仅使用 const 指针访问内容。这听起来可能很复杂,但事实并非如此。只需制作一个薄 wrapper 并添加一个 begin()end()允许迭代的函数:

struct BImpl : B {
virtual ~BImpl() = default;
void addA(std::unique_ptr<A> aObj) override;

ConstPtrVector<A> aObjs() const override {
return aObjs_;
}

private:
std::vector<unique_ptr<A>> aObjs_;
};

ConstPtrVector看起来像这样:

template<typename T>
ConstPtrVector {
ConstPtrVector(const std::vector<T>& vec_) : vec{vec_} {}

MyConstIterator<T> begin() const {
return vec.begin();
}

MyConstIterator<T> end() const {
return vec.end();
}

private:
const std::vector<T>& vec;
};

并且您可以实现 MyConstIterator以一种将指针作为常量返回的方式:

template<typename T>
struct MyConstIterator {
MyConstIterator(std::vector<unique_ptr<T>>::const_iterator it_) : it{std::move(it_)} {}

bool operator==(const MyConstIterator& other) const {
return other.it == it;
}

bool operator!=(const MyConstIterator& other) const {
return other.it != it;
}

const T* operator*() const {
return it->get();
}

const T* operator->() const {
return it->get();
}

MyConstIterator& operator++() {
++it;
return *this;
}

MyConstIterator& operator--() {
--it;
return *this;
}

private:
std::vector<unique_ptr<T>>::const_iterator it;
};

当然,您可以通过实现类似接口(interface)的 vector 来概括此迭代器和包装器。

然后,要使用它,您可以使用基于范围的 for 循环或基于经典迭代器的循环。

顺便说一句:不拥有原始指针并没有错。只要他们仍然不拥有。如果您想避免由于原始指针导致的错误,请查看 observer_ptr<T> ,它可能会有用。

关于c++ - 在抽象接口(interface)中不转移所有权的指针 vector 的常量正确访问器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39257307/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com