У нас есть некоторый код, который выглядит следующим образом:
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
- это означает, чтоэто потребовало бы явного приведения.Я не думаю, что есть прямой способ сделать это - если я ошибаюсь, пожалуйста, скажите мне.
Если не считать этого, я могу придумать два способа решить эту проблему.
- Объявлять (но не определять) закрытые версии
Serializer::Write
для каждого типа, который может быть автоматически преобразован в поддерживаемый нами тип. - Не принимать
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
};
Это довольно дешевое (с точки зрения времени разработки) решение, которое позволяет получить знания о том, на что вы действительно экономитезвонящий и заставляет звонящего знать, что он звонит.