问题
I have an abstract class that declares const and non-const member functions. For the sake of discussion let's say it looks like this:
class record_interface
{
public:
virtual ~record_interface() = default;
virtual void set_foo(BoundedFloat) = 0;
virtual BoundedFloat get_foo() const = 0;
};
This is used as a high-level representation of a record that has different representations when saved to disc and transferred via the wire. So most implementations just need to convert their members to the required high-level representation.
As an example of a valid implementation let's define stored_record
. This is used to store the high-level record in a lossy format:
struct stored_record
{
int16_t foo;
};
It makes sense that stored_record
can implement record_interface
but for various reasons it can't (eg. it needs to be trivially_copyable
). We can make a wrapper that implements the interface for it:
class record_wrapper : public record_interface
{
public:
record_wrapper(stored_record & wrapped)
: wrapped_(wrapped) {}
void set_foo(BoundedFloat value) final { wrapped_.foo = convert_to_int16(value); }
BoundedFloat get_foo() const final { return convert_from_int16(wrapped_.foo); }
private:
stored_record & wrapped_;
};
Now the problem is that we can't use the wrapper when given a const stored_record &
since
the wrapper stores a mutable reference. We also can't make it store a non-const reference as it won't be able to implement the non-const setter function.
Now I was wondering if it would be valid to provide a factory function that const_cast
s away
a const stored_record &
's const
but also returns a const wrapper
so that the reference cannot actually be modified:
record_wrapper make_wrapper(stored_record & wrapped) {return {wrapped}; }
record_wrapper const make_wrapper(stored_record const & wrapped) { return {const_cast<stored_record &>(wrapped)}; }
EDIT: returning a const
record_wrapper
will not really restrict the returned value to be const
, a solution can be to return a const_wrapper<record_wrapper>
or something similar.
Is this a valid usage of const_cast
or is it undefined behaviour due to const_cast
ing away the const
-ness of a reference to an actually const object - even though it is never modified through it.
回答1:
Per https://en.cppreference.com/w/cpp/language/const_cast:
const_cast
makes it possible to form a reference or pointer to non-const type that is actually referring to a const object or a reference or pointer to non-volatile type that is actually referring to a volatile object. Modifying a const object through a non-const access path and referring to a volatile object through a non-volatile glvalue results in undefined behavior.
So, the const_cast
itself is allowed (and well-defined), even though it would be undefined behavior to actually modify the object via the resulting non-const reference.
回答2:
As the other answer is perfecly clear about the validity of const-casting
in your situation, one (sub-)question remains: how make your wrapper
const
when you want it to actually behave as const
? (your edit)
I suggest providing two distinct interfaces, thus two distinct wrappers,
to prevent non-const accesses to the wrapped record when it is thought
about as const.
The drawback of this solution is that, in order to avoid code duplication,
you have to explicitely make the mutable wrapper rely on the const wrapper
(then duplicate the call, not the actual code).
Here is a simple example based on yours:
/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
#include <cstdint>
struct BoundedFloat
{
float f;
};
struct stored_record
{
std::int16_t foo;
};
BoundedFloat
convert_from_int16(std::int16_t v)
{
return {float(v/100.0)};
}
std::int16_t
convert_to_int16(BoundedFloat bf)
{
return {std::int16_t(bf.f*100.0)};
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class const_record_interface
{
public:
virtual ~const_record_interface() = default;
virtual BoundedFloat get_foo() const = 0;
};
class mutable_record_interface : public const_record_interface
{
public:
virtual void set_foo(BoundedFloat) = 0;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class const_record_wrapper : public const_record_interface
{
public:
const_record_wrapper(const stored_record &wrapped) : wrapped_{wrapped} {}
BoundedFloat get_foo() const final { return convert_from_int16(wrapped_.foo); }
private:
const stored_record &wrapped_;
};
const_record_wrapper
make_wrapper(const stored_record &wrapped)
{
return {wrapped};
}
class mutable_record_wrapper : public mutable_record_interface
{
public:
mutable_record_wrapper(stored_record &wrapped) : wrapped_{wrapped} {}
auto as_const() const { return make_wrapper(this->wrapped_); }
void set_foo(BoundedFloat value) final { wrapped_.foo=convert_to_int16(value); }
BoundedFloat get_foo() const final { return as_const().get_foo(); }
private:
stored_record &wrapped_;
};
mutable_record_wrapper
make_wrapper(stored_record &wrapped)
{
return {wrapped};
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int
main()
{
auto sr=stored_record{50};
const auto &csr=sr;
auto w1=make_wrapper(sr);
auto w2=make_wrapper(csr);
std::cout << "w1: " << w1.get_foo().f
<< " w2: " << w2.get_foo().f << '\n';
w1.set_foo({0.6f});
// w2.set_foo({0.7f}); // rejected: no member named ‘set_foo'
std::cout << "w1: " << w1.get_foo().f
<< " w2: " << w2.get_foo().f << '\n';
return 0;
}
来源:https://stackoverflow.com/questions/62551010/is-const-casting-away-const-ness-of-references-to-actual-const-objects-permitted