Как создать быструю константную корректную буферную оболочку класса 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;
}
Другие вопросы по тегам