返回unique_ptr的不可变向量

问题描述 投票:0回答:1

我有以下对象:

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
的代码,可能会出现一些意想不到的副作用,但我对此表示同意。


[编辑]这与深层复制的概念有些相似。按照此处的术语引导:

c++ pointers casting immutability const-pointer
1个回答
0
投票

我认为在不创建自己的代理类的情况下,你能做的最好的事情就是返回转换为常量的项目的范围视图,例如

#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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.