问题
Given the classes
struct Data
{
void bar() const;
void baz();
}
class Foo
{
std::vector<Data> data;
std::map<size_t, Data> indexed_data;
}
I'd like to implement something in class Foo so that I can do the following:
int main()
{
Foo foo;
for(const auto& data : foo.data())
data.bar();
for(auto& data : foo.indexed_data())
data.baz();
const auto& foo_ref = foo;
for(auto& data : foo_ref.data())
data.baz(); // constness violated, shouldn't compile
}
However, I don't wanna expose the class internals by just returning references to the containers. I might also work with classes where the range I'd like to iterate over isn't implemented as a container. So I basically want to create some sort of proxy object which is just a little more than a wrapper to a begin/end pair so that I can iterate over multiple things inside my class. Additionally I would like it to be const correct as displayed above. Is there any well-known pattern to realize this?
回答1:
Consider three cases.
If you want to give full access to your internal data, just make a function to return it: (simply making the member public is also an option)
class C {
public:
Type& data() { return data_; }
const Type& data() const { return data_; }
private:
Type data_;
};
If you want to give read-only access to your internal data, just drop the non-const overload:
class C {
public:
const Type& data() const { return data_; }
private:
Type data_;
};
If you want to give element-only access to your internal data, i.e., you have mutable access to each individual element (when the C
itself is non-const), but you can't change the container itself (e.g., insert a new element), you need to return a proxy. Since C++20, we can return a std::ranges::ref_view
:
class C {
public:
auto data() { return std::ranges::ref_view(data_); }
auto data() const { return std::ranges::ref_view(data_); }
private:
Type data_;
};
You can use the Ranges library if C++20 is not available. This way, the user can access the individual elements, but cannot change the container itself.
Alternatively, you can write your own (minimalist) proxy:
template <typename R>
class Proxy {
public:
explicit Proxy(R& r) :range{r} {}
auto begin() const { return range.begin(); }
auto end() const { return range.end(); }
private:
R& range;
};
Then you can return Proxy{data_}
:
class C {
public:
auto data() { return Proxy{data_}; }
auto data() const { return Proxy{data_}; }
private:
Type data_;
};
Prior to C++17, you can write it like this without class template argument deduction:
class C {
public:
auto data() { return Proxy< Type>{data_}; }
auto data() const { return Proxy<const Type>{data_}; }
private:
Type data_;
};
来源:https://stackoverflow.com/questions/57152866/how-to-provide-multiple-begin-end-proxies-for-a-class