Заставить вызывающих абонентов внутреннего API использовать типы фиксированного размера? - PullRequest
2 голосов
/ 02 февраля 2012

У нас есть некоторый код, который выглядит следующим образом:

class Serializer
{
public:
    template<class Type> void Write(const Type& value)
    {
        internal_write((byte*)value, sizeof(Type));
    }

    // some overloads of Write that take care of some tricky type we have defined

private:
    // implementation of internal_write
};

Как можно догадаться, это записывает данные на диск.У нас есть похожие функции чтения, которые более или менее приводят некоторые байты к типу.Не так надежно, как могло бы быть, но это работает, потому что мы пишем на той же платформе, что и читаем - это означает, что записываемые нами байты соответствуют тому, что нам нужно прочитать.

Теперь мы переходим к поддержке несколькихплатформ.В нескольких местах у нас есть код, подобный следующему:

unsigned long trust_me_this_will_be_fine = get_unsigned_long();
a_serializer.Write(trust_me_this_will_be_fine);

, который отлично работает в современном мире, но если мы предположим, что одна из платформ, которые мы хотим поддерживать, имеет unsigned long как 32-битную, а на другой они64-битные, мы скрыты.

Я бы хотел изменить Serializer::Write, чтобы в качестве параметров принимались только типы с явно заданным размером.Я думал об этом:

class Serializer
{
public:
   void Write(uint32_t value) { ... }
   void Write(uint64_t value) { ... }
};

Но я не думаю, что это действительно решит проблему, потому что в 32-битной системе unsigned long будет автоматически преобразован в uint32_t, но на 64система будет автоматически преобразована в uint64_t.

. Здесь я действительно хочу, чтобы Write(uint32_t) только принимала параметры типа uint32_t - это означает, чтоэто потребовало бы явного приведения.Я не думаю, что есть прямой способ сделать это - если я ошибаюсь, пожалуйста, скажите мне.

Если не считать этого, я могу придумать два способа решить эту проблему.

  1. Объявлять (но не определять) закрытые версии Serializer::Write для каждого типа, который может быть автоматически преобразован в поддерживаемый нами тип.
  2. Не принимать uint32_t напрямую, но классон содержит uint32_t и имеет явный конструктор только для uint32_t.

Вариант 2 будет выглядеть примерно так:

class only_uint32
{
public:
    uint32_t _value;
    explicit only_uint32(uint_32 value) : _value(value) { }
};

class Serializer
{
public:
    void Write(only_uint32 value) { ... }
};

Тогда вызывающий код выглядит так:

unsigned long might_just_work = get_unsigned_long();
a_serializer.Write(static_cast<uint32_t>(might_just_work)); // should work, and be explicitly sized.
a_serializer.Write(might_just_work); // won't compile

Я предполагаю, что многие люди решили этот типпроблемы.Есть ли предпочтительный способ сделать это, о котором я не думал?Является ли одна из моих идей ужасной, замечательной, работоспособной, чем-нибудь?

PS: Да, я понимаю, что это очень длинный пост.Это довольно сложная и подробная проблема.

Обновление:

Спасибо за идеи и помощь.Я думаю, что мы идем с решением, подобным этому:

class Serializer
{
public:
    template<class Type> void Write32(const Type& value)
    {
        static_assert(sizeof(Type) == 4, "Write32 must be called on a 32-bit value.");
        internal_write(reinterpret_cast<byte*>(value), 4);
    }
    // overloads like Write64 and various tricky types as before.

private:
    // implementation of internal_write
};

Это довольно дешевое (с точки зрения времени разработки) решение, которое позволяет получить знания о том, на что вы действительно экономитезвонящий и заставляет звонящего знать, что он звонит.

Ответы [ 3 ]

2 голосов
/ 02 февраля 2012

Вы можете сделать что-то вроде этого:

template<typename T>
class MyClass {
public:
    static_assert(sizeof(T) == sizeof(uint32_t), "Invalid type");
    MyClass(T t) : data(t) { }

private:
    uint32_t data;
};

Или, если вы хотите принять только uint32_t с:

template<typename T>
class MyClass {
public:
    static_assert(is_same<T, uint32_t>::value, "Invalid type");
    MyClass(T t) : data(t) { }

private:
    uint32_t data;
};
2 голосов
/ 02 февраля 2012

Вы можете использовать статическое утверждение, см. http://en.wikipedia.org/wiki/C%2B%2B11#Static_assertions для предотвращения использования вызывающими типами «неправильного размера». Это доступно в C ++ 11, если ваш компилятор не поддерживает его, вы можете использовать http://www.boost.org/doc/libs/1_48_0/doc/html/boost_staticassert.html, который делает то же самое.

1 голос
/ 02 февраля 2012

Вы можете использовать указатель для следующих встроенных функций:

void Write(const uint32_t* const value);
void Write(const uint64_t* const value);

используется:

const uint8_t a= …;
ser.Write(a);  // oops!
ser.Write(&a); // oops!

Или вы можете использовать ссылку, которую нельзя продвигать (то есть передать непостоянную ссылку, если тип встроенный), но это плохо и немного шумно.

Это работает, потому что:

  • значение, константное значение или константная ссылка могут быть повышены или сужены до другого типа, если они конвертируемы.
  • указатель, константный указатель и неконстантная ссылка не могут быть повышены. исключение: Если вы используете старую версию VS, она допускает повышение цены до изменяемой ссылки (нестандартное).
...