C++ 中包装器 类 的大小写相关可变性(例如 uint8_t 数组包装器)
case dependent mutability of wrapper classes in C++ (e.g. uint8_t array wrapper)
如何为
的引用对象(如在关联中,而不是组合中)定义包装器
如果引用的对象本身是 const
,则 - 是或表现为
const
- 如果引用的对象也是可变的,那么它是可变的吗?
我的具体问题:
我正在编写一个内部处理 POD uint8_t[]
数组的函数,但应该使用包装器 class like
与外界接口
class BufferWrapper
{
public:
BufferWrapper(uint8_t* pau8, size_t ui) : m_pau8{pau8}, m_uiSize{ui} {}
uint8_t& operator[](size_t ui) { return m_pau8[ui]; }
const uint8_t& operator[](size_t ui) const { return m_pau8[ui]; }
size_t length() const { return m_uiSize; }
/* other convenience functions ... */
private:
uint8_t* m_pau8;
size_t m_uiSize;
};
我写了一个方便的从uint8_t[SIZE]
到BufferWrapper的转换模板函数。 (我知道它只适用于在编译时已知大小的数组。)
template<typename T> BufferWrapper wrapArray(T& t)
{
return BufferWrapper(t, sizeof(t));
}
只要数组是可变的,这就可以很好地工作,但如果实际数据源是 const uint8_t[]
,则显然无法编译,因为调用 BufferWrapper
构造函数会丢弃 const
源数组的性。
我想要的是一个引用 const uint8_t[]
的 const BufferWrapper
对象,但不应隐式更改为非 const
.
我想出了通过使用 const T&
参数类型重载函数并在内部使用 const_cast
来编译的代码。
template<typename T> const BufferWrapper wrapArray(const T& t)
{
return BufferWrapper(const_cast<T&>(t), sizeof(t));
}
然而,这是一个糟糕的解决方案,因为在复制构造另一个对象时 const
return 类型被删除,例如
BufferWrapper newObject = wrapArray(my_const_uint8_array);
编译,即使它不应该编译。
对于类似的问题,我找到了两种不同的解决方案:
- How to deal with initialization of non-const reference member in const object? 建议通过使用多重继承来解决类似的问题,但这听起来相当复杂,在我的例子中,一些嵌入式编译器不能很好地处理多重继承。
- Why does C++ not have a const constructor? 也解决了
const
消失的问题,但这是一个理解问题,解决方案更多的是解释,而不是这里问题的解决方案。
你有更好的解决方案吗?
这是一个独立的工作示例
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
class BufferWrapper
{
public:
BufferWrapper(uint8_t* pau8, size_t ui) : m_pau8{pau8}, m_uiSize{ui} {}
void fill(uint8_t u8) { memset(m_pau8, u8, m_uiSize); }
uint8_t& operator[](size_t ui) { return m_pau8[ui]; }
const uint8_t& operator[](size_t ui) const { return m_pau8[ui]; }
size_t length() const { return m_uiSize; }
bool operator==(const BufferWrapper& rcco)
{
return (m_uiSize == rcco.m_uiSize) // size equality
&& (0 == memcmp(m_pau8, rcco.m_pau8, m_uiSize)); // and content equality
}
private:
uint8_t* m_pau8;
size_t m_uiSize;
};
// example for data consumer that accepts a BufferWrapper object
void readDataFromBuffer(const BufferWrapper& rcco)
{
for(size_t ui=0; ui<rcco.length(); ++ui)
{
printf("%02x", rcco[ui]);
}
printf("\n");
}
// convenience function to capture length of arrays
// (only works on arrays, not on pointers -- I know)
template<typename T> BufferWrapper wrapArray(T& t)
{
printf("BufferWrapper, ptr=%p, size=%zu\n", &t, sizeof(t));
return BufferWrapper(t, sizeof(t));
}
template<typename T> const BufferWrapper wrapArray(const T& t)
{
printf("const BufferWrapper, ptr=%p, size=%zu\n", &t, sizeof(t));
return BufferWrapper(const_cast<T&>(t), sizeof(t));
}
int main()
{
uint8_t au8[] = { 0xde, 0xad, 0xbe, 0xef };
constexpr uint8_t cau8[] = { 0xba, 0xaa, 0xad, 0xc0, 0xde };
readDataFromBuffer(wrapArray(au8));
readDataFromBuffer(wrapArray(cau8));
// this should _not_ compile, as it casts away the const of the Buffer object
BufferWrapper coFoo = wrapArray(cau8);
coFoo[0] = 0xde;
coFoo[1] = 0xee;
readDataFromBuffer(coFoo);
return 0;
}
Output(可以看出实际上const
变量的内容被改变了):
$ clang -Wall toy_example.cpp
$ ./a.out
BufferWrapper, ptr=0x7fff647544d4, size=4
deadbeef
const BufferWrapper, ptr=0x7fff647544cc, size=5
baaaadc0de
const BufferWrapper, ptr=0x7fff647544cc, size=5
deeeadc0de
通常所做的是为非 const 包装器和 const 设置不同的类型。在您的情况下,名称可能是 BufferWrapper
和 ConstBufferWrapper
,其中后者包含指向 const 的指针而不是指向非常量的指针。前者 class 可以隐式转换为后者。使用模板,甚至不需要任何重复。
P.S。 %02x
是 std::uint8_t
的无效格式说明符,因此程序的行为未定义(除了 UB 修改常量缓冲区)。同样 %p
是 const unsigned char (*)[N]
.
的无效格式说明符
P.P.S。该标准有一个通用包装器,例如您的 BufferWrapper
自 C++20 起名为 std::span
。然而,它确实共享 BufferWrapper
的属性,即它不会将常量传播到指向的对象。
因此,您可以使用预先存在的 span 实现:
void readDataFromBuffer(std::span<const std::uint8_t> rcco) {
for(unsigned u : rcco) { // note the correct type for %02x
std::printf("%02x", u);
}
std::printf("\n");
}
// ...
readDataFromBuffer(au8); // OK
readDataFromBuffer(cau8); // OK
这两个都无法按要求编译:
std::span<const std::uint8_t> coFoo = cau8; // OK
coFoo[0] = 0xde; // does not compile
// alternative
std::span<std::uint8_t> coFoo = cau8; // does not compile
coFoo[0] = 0xde; // would have been OK
eerorika 的回答实际上已经解决了这个问题——我也会使用 C++20 中的 std::span
。
作为参考,我仍在努力更新示例代码,首先展示完整的工作代码,其次解决以下问题:
- 使用重载的转换运算符自动从非 const 转换为 const 引用类型
operator TArrayWrapper<const U>()
- 我使用的
BufferWrapper
class 有一些方便的函数(此处:fill()
)或仅对非常量引用类型有意义的 to-const 类型转换运算符.我不确定使用模板 classes 是否有效,其中某些类型仅支持给定方法的一个子集。这就是为什么我想尝试 eran 的建议,即使用 std::enable_if
来有条件地包含某些方法。
- (在没有 C++20 支持的情况下在编译器上运行的东西)
作为旁注:
我将名称 BufferWrapper
替换为 TArrayWrapper
(并且将 memcmp
/memset
替换为显式循环,这样它也允许对其他类型进行通用使用)以表明它可以在数组上工作uint8_t
以外的类型。
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <type_traits>
template<typename T> class TArrayWrapper
{
public:
TArrayWrapper(T* pT, size_t ui) : m_pT{pT}, m_uiSize{ui} {}
template<typename U = T, typename std::enable_if<std::is_const<U>::value == false, int>::type = 0>
void fill(T t)
{
for(size_t ui=0; ui<m_uiSize; ++ui)
{
m_pT[ui] = t;
}
}
template<typename U = T, typename std::enable_if<std::is_const<U>::value == false, int>::type = 0>
T& operator[](size_t ui) { return m_pT[ui]; }
const T& operator[](size_t ui) const { return m_pT[ui]; }
template<typename U = T, typename std::enable_if<std::is_const<U>::value == false, int>::type = 0>
operator TArrayWrapper<const U>() { return TArrayWrapper<const U>(m_pT, m_uiSize); }
size_t length() const { return m_uiSize; }
bool operator==(const TArrayWrapper<T>& rcco)
{
if(m_uiSize != rcco.m_uiSize)
{
return false;
}
for(size_t ui=0; ui<m_uiSize; ++ui)
{
if (m_pT[ui] != rcco.m_pT[ui])
{
return false;
}
}
return true;
}
private:
T* m_pT;
size_t m_uiSize;
};
using ConstBufferWrapper = TArrayWrapper<const uint8_t>;
using BufferWrapper = TArrayWrapper<uint8_t>;
void readDataFromBuffer(const ConstBufferWrapper& rcco)
{
for(size_t ui=0; ui<rcco.length(); ++ui)
{
printf("%02" PRIx8, rcco[ui]);
}
printf("\n");
}
// convenience function to capture length of arrays
template<typename T> TArrayWrapper<typename std::remove_extent<T>::type> wrapArray(T& t)
{
printf("TArrayWrapper, ptr=%p, size=%zu\n", static_cast<const void*>(&t), sizeof(t));
return TArrayWrapper<typename std::remove_extent<T>::type>(t, sizeof(t));
}
int main()
{
uint8_t au8[] = { 0xde, 0xad, 0xbe, 0xef };
constexpr uint8_t cau8[] = { 0xba, 0xaa, 0xad, 0xc0, 0xde };
readDataFromBuffer(wrapArray(au8));
readDataFromBuffer(wrapArray(cau8));
BufferWrapper coTest = wrapArray(au8);
coTest.fill(0xff);
readDataFromBuffer(coTest);
ConstBufferWrapper coFoo = wrapArray(cau8);
//coFoo[0] = 0xde; // does not compile, as desired:
//coFoo[1] = 0xee; // coFoo shall not allow write access to referenced buffer
readDataFromBuffer(coFoo);
return 0;
}
如何为
的引用对象(如在关联中,而不是组合中)定义包装器-
如果引用的对象本身是
- 是或表现为
const
- 如果引用的对象也是可变的,那么它是可变的吗?
const
,则 我的具体问题:
我正在编写一个内部处理 POD uint8_t[]
数组的函数,但应该使用包装器 class like
class BufferWrapper
{
public:
BufferWrapper(uint8_t* pau8, size_t ui) : m_pau8{pau8}, m_uiSize{ui} {}
uint8_t& operator[](size_t ui) { return m_pau8[ui]; }
const uint8_t& operator[](size_t ui) const { return m_pau8[ui]; }
size_t length() const { return m_uiSize; }
/* other convenience functions ... */
private:
uint8_t* m_pau8;
size_t m_uiSize;
};
我写了一个方便的从uint8_t[SIZE]
到BufferWrapper的转换模板函数。 (我知道它只适用于在编译时已知大小的数组。)
template<typename T> BufferWrapper wrapArray(T& t)
{
return BufferWrapper(t, sizeof(t));
}
只要数组是可变的,这就可以很好地工作,但如果实际数据源是 const uint8_t[]
,则显然无法编译,因为调用 BufferWrapper
构造函数会丢弃 const
源数组的性。
我想要的是一个引用 const uint8_t[]
的 const BufferWrapper
对象,但不应隐式更改为非 const
.
我想出了通过使用 const T&
参数类型重载函数并在内部使用 const_cast
来编译的代码。
template<typename T> const BufferWrapper wrapArray(const T& t)
{
return BufferWrapper(const_cast<T&>(t), sizeof(t));
}
然而,这是一个糟糕的解决方案,因为在复制构造另一个对象时 const
return 类型被删除,例如
BufferWrapper newObject = wrapArray(my_const_uint8_array);
编译,即使它不应该编译。
对于类似的问题,我找到了两种不同的解决方案:
- How to deal with initialization of non-const reference member in const object? 建议通过使用多重继承来解决类似的问题,但这听起来相当复杂,在我的例子中,一些嵌入式编译器不能很好地处理多重继承。
- Why does C++ not have a const constructor? 也解决了
const
消失的问题,但这是一个理解问题,解决方案更多的是解释,而不是这里问题的解决方案。
你有更好的解决方案吗?
这是一个独立的工作示例
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
class BufferWrapper
{
public:
BufferWrapper(uint8_t* pau8, size_t ui) : m_pau8{pau8}, m_uiSize{ui} {}
void fill(uint8_t u8) { memset(m_pau8, u8, m_uiSize); }
uint8_t& operator[](size_t ui) { return m_pau8[ui]; }
const uint8_t& operator[](size_t ui) const { return m_pau8[ui]; }
size_t length() const { return m_uiSize; }
bool operator==(const BufferWrapper& rcco)
{
return (m_uiSize == rcco.m_uiSize) // size equality
&& (0 == memcmp(m_pau8, rcco.m_pau8, m_uiSize)); // and content equality
}
private:
uint8_t* m_pau8;
size_t m_uiSize;
};
// example for data consumer that accepts a BufferWrapper object
void readDataFromBuffer(const BufferWrapper& rcco)
{
for(size_t ui=0; ui<rcco.length(); ++ui)
{
printf("%02x", rcco[ui]);
}
printf("\n");
}
// convenience function to capture length of arrays
// (only works on arrays, not on pointers -- I know)
template<typename T> BufferWrapper wrapArray(T& t)
{
printf("BufferWrapper, ptr=%p, size=%zu\n", &t, sizeof(t));
return BufferWrapper(t, sizeof(t));
}
template<typename T> const BufferWrapper wrapArray(const T& t)
{
printf("const BufferWrapper, ptr=%p, size=%zu\n", &t, sizeof(t));
return BufferWrapper(const_cast<T&>(t), sizeof(t));
}
int main()
{
uint8_t au8[] = { 0xde, 0xad, 0xbe, 0xef };
constexpr uint8_t cau8[] = { 0xba, 0xaa, 0xad, 0xc0, 0xde };
readDataFromBuffer(wrapArray(au8));
readDataFromBuffer(wrapArray(cau8));
// this should _not_ compile, as it casts away the const of the Buffer object
BufferWrapper coFoo = wrapArray(cau8);
coFoo[0] = 0xde;
coFoo[1] = 0xee;
readDataFromBuffer(coFoo);
return 0;
}
Output(可以看出实际上const
变量的内容被改变了):
$ clang -Wall toy_example.cpp
$ ./a.out
BufferWrapper, ptr=0x7fff647544d4, size=4
deadbeef
const BufferWrapper, ptr=0x7fff647544cc, size=5
baaaadc0de
const BufferWrapper, ptr=0x7fff647544cc, size=5
deeeadc0de
通常所做的是为非 const 包装器和 const 设置不同的类型。在您的情况下,名称可能是 BufferWrapper
和 ConstBufferWrapper
,其中后者包含指向 const 的指针而不是指向非常量的指针。前者 class 可以隐式转换为后者。使用模板,甚至不需要任何重复。
P.S。 %02x
是 std::uint8_t
的无效格式说明符,因此程序的行为未定义(除了 UB 修改常量缓冲区)。同样 %p
是 const unsigned char (*)[N]
.
P.P.S。该标准有一个通用包装器,例如您的 BufferWrapper
自 C++20 起名为 std::span
。然而,它确实共享 BufferWrapper
的属性,即它不会将常量传播到指向的对象。
因此,您可以使用预先存在的 span 实现:
void readDataFromBuffer(std::span<const std::uint8_t> rcco) {
for(unsigned u : rcco) { // note the correct type for %02x
std::printf("%02x", u);
}
std::printf("\n");
}
// ...
readDataFromBuffer(au8); // OK
readDataFromBuffer(cau8); // OK
这两个都无法按要求编译:
std::span<const std::uint8_t> coFoo = cau8; // OK
coFoo[0] = 0xde; // does not compile
// alternative
std::span<std::uint8_t> coFoo = cau8; // does not compile
coFoo[0] = 0xde; // would have been OK
eerorika 的回答实际上已经解决了这个问题——我也会使用 C++20 中的 std::span
。
作为参考,我仍在努力更新示例代码,首先展示完整的工作代码,其次解决以下问题:
- 使用重载的转换运算符自动从非 const 转换为 const 引用类型
operator TArrayWrapper<const U>()
- 我使用的
BufferWrapper
class 有一些方便的函数(此处:fill()
)或仅对非常量引用类型有意义的 to-const 类型转换运算符.我不确定使用模板 classes 是否有效,其中某些类型仅支持给定方法的一个子集。这就是为什么我想尝试 eran 的建议,即使用std::enable_if
来有条件地包含某些方法。 - (在没有 C++20 支持的情况下在编译器上运行的东西)
作为旁注:
我将名称 BufferWrapper
替换为 TArrayWrapper
(并且将 memcmp
/memset
替换为显式循环,这样它也允许对其他类型进行通用使用)以表明它可以在数组上工作uint8_t
以外的类型。
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <type_traits>
template<typename T> class TArrayWrapper
{
public:
TArrayWrapper(T* pT, size_t ui) : m_pT{pT}, m_uiSize{ui} {}
template<typename U = T, typename std::enable_if<std::is_const<U>::value == false, int>::type = 0>
void fill(T t)
{
for(size_t ui=0; ui<m_uiSize; ++ui)
{
m_pT[ui] = t;
}
}
template<typename U = T, typename std::enable_if<std::is_const<U>::value == false, int>::type = 0>
T& operator[](size_t ui) { return m_pT[ui]; }
const T& operator[](size_t ui) const { return m_pT[ui]; }
template<typename U = T, typename std::enable_if<std::is_const<U>::value == false, int>::type = 0>
operator TArrayWrapper<const U>() { return TArrayWrapper<const U>(m_pT, m_uiSize); }
size_t length() const { return m_uiSize; }
bool operator==(const TArrayWrapper<T>& rcco)
{
if(m_uiSize != rcco.m_uiSize)
{
return false;
}
for(size_t ui=0; ui<m_uiSize; ++ui)
{
if (m_pT[ui] != rcco.m_pT[ui])
{
return false;
}
}
return true;
}
private:
T* m_pT;
size_t m_uiSize;
};
using ConstBufferWrapper = TArrayWrapper<const uint8_t>;
using BufferWrapper = TArrayWrapper<uint8_t>;
void readDataFromBuffer(const ConstBufferWrapper& rcco)
{
for(size_t ui=0; ui<rcco.length(); ++ui)
{
printf("%02" PRIx8, rcco[ui]);
}
printf("\n");
}
// convenience function to capture length of arrays
template<typename T> TArrayWrapper<typename std::remove_extent<T>::type> wrapArray(T& t)
{
printf("TArrayWrapper, ptr=%p, size=%zu\n", static_cast<const void*>(&t), sizeof(t));
return TArrayWrapper<typename std::remove_extent<T>::type>(t, sizeof(t));
}
int main()
{
uint8_t au8[] = { 0xde, 0xad, 0xbe, 0xef };
constexpr uint8_t cau8[] = { 0xba, 0xaa, 0xad, 0xc0, 0xde };
readDataFromBuffer(wrapArray(au8));
readDataFromBuffer(wrapArray(cau8));
BufferWrapper coTest = wrapArray(au8);
coTest.fill(0xff);
readDataFromBuffer(coTest);
ConstBufferWrapper coFoo = wrapArray(cau8);
//coFoo[0] = 0xde; // does not compile, as desired:
//coFoo[1] = 0xee; // coFoo shall not allow write access to referenced buffer
readDataFromBuffer(coFoo);
return 0;
}