我有以下对象:
class Container {
public:
std::vector<std::unique_ptr<Item>>& items() { return m_items; }
private:
std::vector<std::unique_ptr<Item>> m_items;
};
我想使用
const
使整个对象不可变,但仍然提供对 items()
的访问,即使它在内部使用指针。例如。声明 void doSomething(const Container& container)
并确保代码不会意外更改其内容。
如何编写
const auto& items() const { ... }
以允许对项目向量进行只读访问,而调用者无法修改任何项目、指针或向量?
尝试#1:第一步是...
const std::vector<std::unique_ptr<Item>>& items() const { return m_items; }
...
void doSomething(const Container& container)
{
// Good: const container, can't do this
container.items().clear();
// Good: const unique_ptr, can't do this
container.items()[0].reset();
// Bad: non-const items
*container.items()[0] = ...;
}
尝试#2:我真正想要的是这个,但它是UB,因为不能保证const类型具有相同的内存布局(也许还有其他原因?)。
const std::vector<std::unique_ptr<const Item>>& items() const {
return reinterpret_cast<const std::vector<std::unique_ptr<const Item>>&>(m_items);
// ^_________ change unique_ptr type
}
尝试#3:一个相当糟糕的替代方案是构造并返回一个临时向量。同样糟糕的是缓存和维护这个重复的向量。
std::vector<const Item*> items() const {
return {m_items.begin(), m_items.end()};
}
尝试 #4:实现类似
std::span
的东西,但让迭代器仅返回 const 引用。在这种情况下,需要解开 std::unique_ptr。这可行,但对于一个用例来说这是一项相当大的努力。也许也进入了过度工程的领域?
对于需要某些类型且在迭代时不使用
auto
的代码,可能会出现一些意想不到的副作用,但我对此表示同意。
[编辑]这与深层复制的概念有些相似。按照此处的术语引导:
我认为在不创建自己的代理类的情况下,你能做的最好的事情就是返回转换为常量的项目的范围视图,例如
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <ranges>
struct bar {
std::string str;
int num;
};
class foo {
std::vector<std::unique_ptr<bar>> items_;
public:
foo()
{
items_.emplace_back(std::make_unique<bar>("quux", 42));
items_.emplace_back(std::make_unique<bar>("mumble", 12));
items_.emplace_back(std::make_unique<bar>("zzz", 123));
}
auto items() const {
using std::ranges::views::transform;
return items_ |
transform(
[](auto&& itm)->const bar& {
return *itm;
}
);
}
};
int main() {
foo foobar;
for (auto& bar : foobar.items()) {
std::cout << bar.str << " " << bar.num << "\n";
// bar.str = "hello"; // << this is an error
}
}