简体   繁体   中英

Using iterator to retrieve const values pointed to in containers

Const casting container value-types seems not possible. A comment in the other question suggests iterators as a solution, yet does not go into detail. Since I seemingly cannot simply convert a container from a non-const to a const version as a function parameter, I arrive at Iterators to maybe be able to do the job.

I actually have a vector<shared_ptr<Thing> > to be treated as const vector<shared_ptr<Thing const> > . With it I intend to use the shared_ptr<Thing const> as further references in other structures, without allowing those structures to alter the Thing s. Those structures may create their own objects, stored by their own shared_ptr, if they want slightly different content within their containers, while still actively sharing most Things with other objects.

So I would need either shared_ptr<const Thing>& , or const shared_ptr<const Thing>& from an Iterator through the sequence. Either would suffice, but just because one can be indifferent about passing references in this example, because of shared_ptr's copy semantics are about just that. Yet even just using default const_iterator , retrieved by cbegin() , c.end() and such, will give me a const shared_ptr<Thing>& instead.

Edit: To copy the vector element for element would be one way technically, as in the other question, yet undesired for interface reasons. I am going for reinterpretation here, not copy.

Any suggestions on where a workaround might lie?

Copying around all the shared pointers into new vectors can become pretty expensive quickly, especially if the original source vector gets updated frequently and referring instances thus need to fetch updates again and again.

I personally would thus rather provide either a wrapper around std::vector or around std::shared_ptr that give modifying access only to the data owner (who would be a friend then) while the general interface only allows non-modifying access. Wrapping the vector, though, will require the shared pointers to get copied whenever retrieving one, thus involving reference counting, additionally the solution gets more complex, thus going with the wrapper around the shared pointer here:

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;
}

A pretty simple approach might be maintaining a vector of pointers to const already internally – and casting the const away on internal usage.

Warning: Don't consider this as an invitation for doing so carelessly, If you do so, at some point of time you will break something. After all those objects are const for a reason!

In given case, though, this reason is a pure external one – if it wasn't for the public interface the objects wouldn't ever have got const anyway so casting it away again is valid in this very specific case .

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
    }
};

Based on your situation, it sounds like defining a custom iterator with the semantics you want is the safe and simple way to go. It's technically correct, hard to accidentally misuse, and fairly fast, just requiring a shared_ptr copy on iterator dereference.

I always recommend boost::iterator_facade or boost::iterator_adaptor for creating an iterator type. Since it will contain the original vector iterator as a "base" implementation, iterator_adaptor is helpful.

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) {}
};

The reference iterator type would be std::shared_ptr<const Thing>& by default, but in this case it can't be a reference since there is no object of that type. Normally the class would define some of the behavior functions like dereference or increment , but none are needed here: the only change from the base vector iterator is to the return type of operator* , and the default reference dereference() const { return *base_reference(); } reference dereference() const { return *base_reference(); } works fine by implicit conversion from const std::shared_ptr<Thing>& to std::shared_ptr<const Thing> .

The class could also be a template taking Thing as its type parameter, to create multiple iterator types.

Then to provide a container-like view object, we can use C++20's std::ranges::subrange to provide begin() and end() and a few other things helping the out the range templates:

#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())) {}
};

Or if that's not available, a simple class with begin() and end() :

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;
};

Demo on godbolt . (Clang doesn't like the ranges code due to this libstdc++ incompatibility ; I'm not sure how to get godbolt to switch it to clang's libc++.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM