I have a Data class and a Wrapper class which provides methods to access the Data. The WrapperMutable class extends Wrapper to add methods to modify the Data.
#include <memory>
using namespace std;
class Data {
public:
void update(); // non-const method which modifies internal state of Data
};
class Wrapper
{
public:
Wrapper(shared_ptr<Data const> data) : myData(data) {} // accepts const pointer
// ...bunch of functions providing read-only access to aspects of the Data...
protected:
shared_ptr<Data const> myData; // stores const pointer
};
// Extend Wrapper with methods to modify the wrapped Data.
class WrapperMutable : public Wrapper
{
public:
WrapperMutable(shared_ptr<Data> data) : Wrapper(data) {}
// ERROR: invoking non-const method on const object:
void updateData() { myData->update(); }
};
The problem of course is with the const-ness of the wrapped Data object, which means that WrapperMutable can't modify it.
I considered changing Wrapper to accept and store non-const Data, but often the client itself will only have access to const Data, so they would then be forced to const_cast or copy in order to create a Wrapper.
So the only way I've been able to achieve this is by keeping an additional non-const pointer in the WrapperMutable class, and using this in mutable contexts:
class WrapperMutable : public Wrapper
{
public:
WrapperMutable(shared_ptr<Data> data) : Wrapper(data), myMutableData(data) {}
// Use myMutableData instead of the const myData
void updateData() { myMutableData->update(); }
private:
shared_ptr<Data> myMutableData; // non-const pointer to the same Data as in Wrapper
};
Is there a better way? Clearly, deriving WrapperMutable from Wrapper is the source of my problem, but I don't want to have to reimplement all the methods of Wrapper in WrapperMutable either.
Inheritance expresses a "Kind of" relationship.
constness is not a "kind-of" relationship.
A const thing is a very different kind of thing to a mutable thing.
The model of shared_ptr itself shows how to express this relationship. A shared_ptr to mutable Data is convertible to shared_ptr of const Data but not the other way around.
You can express this relationship thus:
#include <iostream>
#include <memory>
struct Data
{
};
struct const_data_wrapper
{
const_data_wrapper(std::shared_ptr<const Data> p) : _impl(std::move(p)) {}
void which() const {
std::cout << "const" << std::endl;
}
private:
std::shared_ptr<const Data> _impl;
};
struct data_wrapper
{
data_wrapper(std::shared_ptr<Data> p) : _impl(std::move(p)) {}
const_data_wrapper as_const() const {
return const_data_wrapper(_impl);
}
void which() const {
std::cout << "not const" << std::endl;
}
private:
std::shared_ptr<Data> _impl;
};
using namespace std;
auto main() -> int
{
auto w1 = data_wrapper(make_shared<Data>());
auto w2 = w1.as_const();
w1.which();
w2.which();
return 0;
}
output:
not const
const
I think, you don't need a shared_ptr<> in WrapperMutable, a raw pointer will do:
class WrapperMutable : public Wrapper
{
public:
WrapperMutable(Data* data):
Wrapper{shared_ptr<Data const>{data}},
myMutableData{data} {}
// Use myMutableData instead of the const myData
void updateData() { myMutableData->update(); }
private:
Data* myMutableData; // non-const pointer to the same Data as in Wrapper
};
At least that saves you from incrementing and decrementing the reference counter.
From a software design point of view, are you sure, that WrapperMutable "is a" Wrapper? My gutt feeling says, that you are breaking the single responsibility principle somewhere. That is among the most reasons for design issues I have seen.
BTW.: Please reconsider, if a shared_ptr<> really is what you need here. It is often overused as a replacement for garbage collection. Use it, when you really want to state shared ownership. Prefer raw pointers for no ownership and unique_ptr<> for unique ownership. The reason is, that shared_ptr<> in contrast to raw pointers and unique_ptr<> is not free of cost. It reuquires an additional increment and decrement of the reference counter and usually an additional level of indirection when you dereference it. unique_ptr<> on the other hand will create the same code as a raw pointer with correctly places deletes all over the place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With