[英]Using iterator to retrieve const values pointed to in containers
const 转换容器值类型似乎是不可能的。 另一个问题中的评论建议将迭代器作为解决方案,但并未详细说明 go。 由于我似乎不能简单地将容器从非 const 转换为 const 版本作为 function 参数,我到达迭代器也许能够完成这项工作。
我实际上有一个vector<shared_ptr<Thing> >
被视为const vector<shared_ptr<Thing const> >
。 有了它,我打算将shared_ptr<Thing const>
用作其他结构中的进一步引用,而不允许这些结构改变Thing
。 这些结构可以创建自己的对象,由它们自己的 shared_ptr 存储,如果它们想要在它们的容器中稍微不同的内容,同时仍然积极地与其他对象共享大多数Things
。
所以我需要从迭代器通过序列的shared_ptr<const Thing>&
或const shared_ptr<const Thing>&
。 任何一种都足够了,但只是因为在这个例子中传递引用可以无所谓,因为 shared_ptr 的复制语义就是这样。 然而,即使只使用由cbegin()
、 c.end()
等检索的默认const_iterator
,也会给我一个const shared_ptr<Thing>&
。
编辑:复制元素的矢量元素在技术上是一种方式,就像在另一个问题中一样,但由于界面原因是不受欢迎的。 我要在这里重新解释,而不是复制。
关于解决方法可能存在的任何建议?
将所有共享指针复制到新向量中很快就会变得非常昂贵,特别是如果原始源向量被频繁更新并且引用实例因此需要一次又一次地获取更新。
因此,我个人宁愿提供一个围绕std::vector
或围绕std::shared_ptr
的包装器,它只向数据所有者(届时将成为朋友)提供修改访问权限,而通用接口只允许非修改访问。 但是,包装向量将需要在检索共享指针时复制共享指针,从而涉及引用计数,此外,解决方案变得更加复杂,因此在此处使用共享指针周围的包装器:
struct Thing
{
void doSomething() { }
void doSomethingElse() const { }
};
class DataOwner
{
public:
class SharedPointer
{
public:
SharedPointer(SharedPointer const&) = default;
SharedPointer(SharedPointer&&) = default;
SharedPointer& operator=(SharedPointer const&) = default;
SharedPointer& operator=(SharedPointer&&) = default;
Thing const& operator*() const
{
return *m_data;
}
Thing const* operator->() const
{
return m_data.get();
}
Thing const* get() const
{
return m_data.get();
}
// should be all of the public interface needed...
private:
friend class DataOwner;
SharedPointer(Thing* t) : m_data(t) { }
std::shared_ptr<Thing> m_data;
};
std::vector<SharedPointer> const& data() const
{
return m_data;
}
void modify()
{
m_data.emplace_back(SharedPointer(new Thing()));
m_data[0].m_data->doSomething();
// if needed at many places you might want to have a
// convenience function, see below...
at(0)->doSomething();
}
// for convenience of the user you might optionally duplicate
// the const interface of `std::vector` here as well
private:
std::vector<SharedPointer> m_data;
Thing* at(size_t index)
{
return m_data[index].m_data.get();
}
};
int main()
{
DataOwner o;
o.modify();
o.data()[0]->doSomethingElse();
// o.data()[0]->doSomething(); won't compile!
return 0;
}
一个非常简单的方法可能是在内部维护一个指向const
的指针向量 - 并在内部使用时将const
丢弃。
警告:不要将此视为粗心大意的邀请,如果您这样做,在某些时候您会破坏某些东西。 毕竟这些对象都是const
是有原因的!
但是,在特定情况下,这个原因是纯粹的外部原因——如果不是公共接口,对象将永远不会得到const
,因此在这种非常特殊的情况下再次将其丢弃是有效的。
class DataOwner
{
public:
std::vector<std::shared_ptr<Thing const>> const& data() const
{
return m_data;
}
void modify()
{
m_data.emplace_back(new Thing());
at(0)->doSomething();
}
// for convenience of the user you might optionally duplicate
// the const interface of `std::vector` here as well
private:
std::vector<std::shared_ptr<Thing const>> m_data;
Thing* at(size_t index)
{
// only one single location where const-casting
// remember: generally a dangerous operation,
// here one of the view valid use cases
return const_cast<Thing*>(m_data[index].get());
// don't forget to document in your own code WHY it is valid here
}
};
根据您的情况,听起来像定义具有所需语义的自定义迭代器是 go 的安全且简单的方法。 它在技术上是正确的,很难意外误用,而且相当快,只需要在迭代器取消引用时使用shared_ptr
副本。
我总是推荐boost::iterator_facade
或boost::iterator_adaptor
来创建迭代器类型。 由于它将包含原始vector
迭代器作为“基础”实现,因此iterator_adaptor
很有帮助。
class const_Thing_ptr_iterator :
public boost::iterator_adaptor<
const_Thing_ptr_iterator, // CRTP derived type
std::vector<std::shared_ptr<Thing>>::const_iterator, // base iterator type
std::shared_ptr<const Thing>, // value_type
std::random_access_iterator_tag, // traversal type
std::shared_ptr<const Thing> // reference
>
{
public:
const_Thing_ptr_iterator() = default;
explicit const_Thing_ptr_iterator(base_type iter)
: iterator_adaptor(iter) {}
};
reference
迭代器类型默认为std::shared_ptr<const Thing>&
,但在这种情况下它不能是引用,因为没有该类型的 object 。 通常 class 会定义一些行为函数,如dereference
或increment
,但这里不需要:从基本向量迭代器的唯一变化是operator*
的返回类型,默认reference dereference() const { return *base_reference(); }
通过从const std::shared_ptr<Thing>&
reference dereference() const { return *base_reference(); }
std::shared_ptr<const Thing>
的隐式转换可以正常工作。
class 也可以是一个模板,以Thing
作为其类型参数,以创建多个迭代器类型。
然后为了提供类似容器的视图 object,我们可以使用 C++20 的std::ranges::subrange
来提供begin()
和end()
以及其他一些帮助范围模板的东西:
#include <ranges>
class const_Thing_ptrs_view
: public std::ranges::subrange<const_Thing_ptr_iterator>
{
public:
explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
: subrange(const_Thing_ptr_iterator(vec.begin()),
const_Thing_ptr_iterator(vec.end())) {}
};
或者,如果这不可用,则使用带有begin()
和end()
的简单 class :
class const_Thing_ptrs_view {
public:
explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
: m_begin(vec.begin()), m_end(vec.end()) {}
const_Thing_ptr_iterator begin() const { return m_begin; }
const_Thing_ptr_iterator end() const { return m_end; }
private:
const_Thing_ptr_iterator m_begin;
const_Thing_ptr_iterator m_end;
};
在godbolt上演示。 (由于libstdc++ 不兼容,Clang 不喜欢范围代码;我不确定如何让 Godbolt 将其切换到 clang 的 libc++。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.