如果从未通过它们进行修改,是否允许对实际 const 对象的引用的 const 性?
Is const-casting away const-ness of references to actual const objects permitted if they are never modified through them?
我有一个抽象 class 声明了 const 和非常量成员函数。为了便于讨论,假设它看起来像这样:
class record_interface
{
public:
virtual ~record_interface() = default;
virtual void set_foo(BoundedFloat) = 0;
virtual BoundedFloat get_foo() const = 0;
};
这用作记录的高级表示,在保存到光盘和通过有线传输时具有不同的表示。所以大多数实现只需要将它们的成员转换为所需的高级表示。
作为有效实现的示例,让我们定义 stored_record
。这用于以有损格式存储高级记录:
struct stored_record
{
int16_t foo;
};
stored_record
可以实现 record_interface
是有道理的,但由于各种原因它不能(例如,它需要 trivially_copyable
)。我们可以制作一个为其实现接口的包装器:
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_;
};
现在的问题是我们不能在给定 const stored_record &
时使用包装器,因为
包装器存储一个可变引用。我们也不能让它存储非常量引用,因为它无法实现非常量 setter 函数。
现在我想知道提供一个 const_cast
消失的工厂函数是否有效
a const stored_record &
的 const
还有 returns a const wrapper
以便实际上不能修改引用:
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
不会真正限制 returned 值是 const
,一个解决方案可以是 return 一个 const_wrapper<record_wrapper>
或类似的东西。
这是 const_cast
的有效用法还是由于 const_cast
消除了对实际 const 对象的引用的 const
-ness 的未定义行为 - 即使它永远不会通过它修改。
根据 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.
因此,const_cast
本身是允许的(并且定义明确),即使通过生成的非常量引用实际修改对象是未定义的行为。
因为另一个答案非常清楚 const-casting 的有效性
在你的情况下,一个(子)问题仍然存在:如何制作你的包装器
const
当您希望它实际表现为 const
时? (您的编辑)
我建议提供两个不同的接口,即两个不同的包装器,
防止 non-const 在被认为时访问包装记录
关于 const.
这个解决方案的缺点是,为了避免代码重复,
您必须明确地使可变包装器依赖于常量包装器
(然后复制调用,而不是实际代码)。
这是一个基于您的示例的简单示例:
/**
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;
}
我有一个抽象 class 声明了 const 和非常量成员函数。为了便于讨论,假设它看起来像这样:
class record_interface
{
public:
virtual ~record_interface() = default;
virtual void set_foo(BoundedFloat) = 0;
virtual BoundedFloat get_foo() const = 0;
};
这用作记录的高级表示,在保存到光盘和通过有线传输时具有不同的表示。所以大多数实现只需要将它们的成员转换为所需的高级表示。
作为有效实现的示例,让我们定义 stored_record
。这用于以有损格式存储高级记录:
struct stored_record
{
int16_t foo;
};
stored_record
可以实现 record_interface
是有道理的,但由于各种原因它不能(例如,它需要 trivially_copyable
)。我们可以制作一个为其实现接口的包装器:
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_;
};
现在的问题是我们不能在给定 const stored_record &
时使用包装器,因为
包装器存储一个可变引用。我们也不能让它存储非常量引用,因为它无法实现非常量 setter 函数。
现在我想知道提供一个 const_cast
消失的工厂函数是否有效
a const stored_record &
的 const
还有 returns a const wrapper
以便实际上不能修改引用:
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
不会真正限制 returned 值是 const
,一个解决方案可以是 return 一个 const_wrapper<record_wrapper>
或类似的东西。
这是 const_cast
的有效用法还是由于 const_cast
消除了对实际 const 对象的引用的 const
-ness 的未定义行为 - 即使它永远不会通过它修改。
根据 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.
因此,const_cast
本身是允许的(并且定义明确),即使通过生成的非常量引用实际修改对象是未定义的行为。
因为另一个答案非常清楚 const-casting 的有效性
在你的情况下,一个(子)问题仍然存在:如何制作你的包装器
const
当您希望它实际表现为 const
时? (您的编辑)
我建议提供两个不同的接口,即两个不同的包装器,
防止 non-const 在被认为时访问包装记录
关于 const.
这个解决方案的缺点是,为了避免代码重复,
您必须明确地使可变包装器依赖于常量包装器
(然后复制调用,而不是实际代码)。
这是一个基于您的示例的简单示例:
/**
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;
}