Как создать быструю константную корректную буферную оболочку класса C++?
Как создать класс-оболочку для существующего объекта, который автоматически запрещает или разрешает изменение данных обернутого объекта в зависимости от того, был ли обернутый объект предоставлен в конструктор с определителем типа const или без него.
Таким образом, если получен класс-оболочка (WrappedObj *ptr), то он позволяет устанавливать и получать методы. Если получен класс-оболочка (const WrappedObj *ptr), то во время компиляции разрешены только методы get.
Пример проблемы:
У меня есть указатель на буфер, где есть заголовок Ethernet, и я хочу упростить доступ к данным Ethernet для удобочитаемости и уменьшить количество ошибок благодаря порядку байтов.
struct ethhdr {
uint8_t h_dst[6]; /* destination eth addr */
uint8_t h_src[6]; /* source ether addr */
uint16_t h_proto; /* packet type ID field */
uint8_t h_data[0];
} __attribute__((packed));
// My wrapper "view" class, which doesn't really work as expected
class EtherView {
public:
EtherView(uint8_t *ptr) : mPtr{(ethhdr*)ptr} {}
EtherView(const uint8_t *ptr) : mPtr{(ethhdr*)ptr} {}
/* SET METHODS */
void setProtocol(uint16_t proto) {
mPtr->h_proto = htons(proto);
}
/* ........ */
/* CONST GET METHODS */
uint16_t protocol() const {
return ntohs(mPtr->h_proto);
}
/* ........ */
private:
ethhdr *mPtr;
};
int main() {
uint8_t fakeBuffer[128];
EtherView ethView(fakeBuffer);
//OK - we want to modify because fakeBuffer isn't const
ethView.setProtocol(80);
const uint8_t *fakeConstBuff = fakeBuffer;
EtherView constEthView(fakeConstBuff);
// HERE I WANT COMPILE ERROR, because Wrapper was created with const param
constEthView.setProtocol(80);
/* I know its possible to define const wrapper:
const EtherView constEthView(fakeConstBuff);
, but I do not trust to "remember" to do it - it must be automatic. */
return 0;
}
Как вы можете видеть, эта оболочка дает безопасную и, что важно, быструю оболочку для изменения / чтения буфера. Я проверил, и у него такая же производительность, как если бы изменения значений буфера выполнялись inline (потому что они встроены компилятором).
Возможные (не идеальные) решения на данный момент:
1) Не забудьте добавить const для класса-обертки, когда источник данных const.
- Не хочу "помнить" (со временем ошибусь)
2) Создайте базовый класс (EthViewConst) с использованием методов получения и создайте только на основе const. Затем наследуйте этот класс, который конструируется на неконстантном источнике и имеет также функции-установщики. Если других решений не будет, то, вероятно, это будет "лучший" способ сделать это, но:
- Не совсем соответствует требованиям. Я хотел бы иметь один класс, но если это невозможно, то это приемлемо.
3) Имейте некоторую специальную Фабрику, которая создает постоянный или неконстантный объект в зависимости от источника данных. Пример "псевдокода": [EtherView view = EtherView::Create(buffer);], но я не могу найти способ сделать это, потому что:
- возврат константного или неконстантного динамически созданного указателя класса Wrapper будет работать, но не соответствует требованиям: он должен быть быстрым. В этом случае это будет очень дорого.
- возврат const/ неконстантного объекта не работает, потому что "получатель" может определить неконстантный объект, и постоянство будет потеряно при копировании
- возврат не может быть сделан для неконстантного объекта (из-за временного). Может быть, есть какой-то хакерский способ?
4)..... любые другие решения.....??
- переместить строительство?
- идеальная пересылка?
- шаблоны?
- constexpr?
- препроцессор - нет, спасибо
1 ответ
Вероятно, это полное злоупотребление механизмом шаблонов, и ваш пробег будет варьироваться в зависимости от того, генерируются ли неиспользуемые методы шаблона, и, если они есть, как ваш компоновщик справляется с ними. Я скажу, что следующее, скорее всего, не переносимо и, вероятно, безумно. К счастью, вы можете спрятаться за псевдонимами некоторых типов в целях безопасности, если они плохо переносятся.
Этот метод очень громко жалуется, если вы используете const uint16_t*
в изменяемой версии представления или uint16_t*
в const версии представления.
#include <type_traits>
struct ethhdr {
uint8_t h_dst[6]; /* destination eth addr */
uint8_t h_src[6]; /* source ether addr */
uint16_t h_proto; /* packet type ID field */
uint8_t h_data[0];
} __attribute__((packed));
template<typename T, typename U>
class EtherView {
public:
EtherView(T* ptr) : mPtr{(U*)ptr} {}
/* SET METHODS */
void setProtocol(T proto) {
mPtr->h_proto = proto;
}
/* CONST GET METHODS */
T protocol() const {
return mPtr->h_proto;
}
private:
U *mPtr;
};
using MutableEtherView = EtherView<uint16_t, ethhdr>;
using ConstEtherView = EtherView<const uint16_t, const ethhdr>;
int main() {
uint8_t fakeBuffer[128];
MutableEtherView ethView(fakeBuffer);
ethView.setProtocol(80);
const uint8_t *fakeConstBuff = fakeBuffer;
ConstEtherView constEthView(fakeConstBuff);
MutableEtherView mutView(fakeConstBuff); // Generates error here
constEthView.setProtocol(80); // Generates error here
return 0;
}