Pull to refresh

Comments 17

Опасно. Использовать и молиться, что бы ничего в классе не изменилось.
Если библиотека гарантирует совместимость ABI, то пока он не изменится ничего в этом конкретном случае не сломается.
А на случай изменения ABI и перекомпиляции приложения такие костыли нужно оборачивать в static_assert, правда как можно сделать проверку в этом случае я сходу не представляю.
Опасность есть, согласен. Лично для себя (в MSVC12) я использую для проверки вот такой код (его можно вставить в место инициализации программы):

	#ifdef _DEBUG
	DWORD dataOffset;
	__asm
	{
		mov		eax, oracle::occi::Number::data
		mov		[dataOffset], eax
	}

	ASSERT (dataOffset == 0);
	#endif

Дело в том, что студия разрешает ассемблерному коду свободный доступ к private членам класса. Таким образом я получаю смещение члена data, а затем проверяю, что оно равно нулю.
Код так же не дает 100% гарантии — если вдруг в будущей версии под именем data будет совсем другой член. Но это маловероятно.
В отдельной единице трансляции:
#define private public
Так как там есть приватные функции эта замена сломает ABI.
Можно поподробней, почему сломается?
techbase.kde.org/Policies/Binary_Compatibility_Examples#Change_the_access_rights
Может изменится декорированное имя метода (а в MSVC точно изменится).

А вообще, про ABI C++-библиотек есть хорошая документация от KDE: techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++
В С++ есть принцып: не платим за то, что не используем. На этом принципе основаны различные проверки во время компиляции, примеры можно почитать у того же Александреску.

Если мы объявляем функцию
OCINumber* extractOCINumber(oracle::occi::Number& );

а реализацию изолируем в отдельной еденице трансляции extractOCINumber.cpp
#define private public
// подключаем необходимый заголовочный файл
OCINumber* extractOCINumber(oracle::occi::Number& foo)
{
  return &foo.data;
}
//и ничего более

то ничего не сломаем. Зато получаем проверку есть ли член-данное, имеет ли оно нужный нам тип, верное смещение данного в классе.
после:
//и ичего более

я бы добавил:
#undef private

:-)
Да, в таком случае если в этой единице трансляции ничего из методов класса oracle::occi::Number вызываться не будет, то ничего сломаться не должно.
Кажется я немного погорячился жесткостью требований для полей. Но для вызова приватных методов это уже не сработает.
Я всегда был против такого решения. Использовать встроенный asm в данном случае меньшее зло.
На уровне асма и машинных кодов различий между public/private/protected нет, это асбтракции для человека. Но этим ещё и переносимость ломается. Но под задачу.
Кроме перечисленного, Number можно еще преобразовать в строку (toText) или массив (toBytes). Ну а потом уже в int64_t или что-то еще. Да, это медленнее, но зато безопасно.
Ещё есть проблема в
__int64 OCCINumberToInt64 (const oracle::occi::Number &number, OCIError *hError, bool bSigned)
{
    const OCINumber *ociNumber = (const OCINumber*)&number;
    __int64 result;
    OCINumberToInt (hError, ociNumber, 8, bSigned ? OCI_NUMBER_SIGNED : OCI_NUMBER_UNSIGNED, &result);

    return result;
}

вы тут сохраняете в знаковый int64 как знаковые, так и беззнаковые значения. Судя по всему тестировали только на положительных числах?

Возможно стоило бы переписать как-то так (C++11 доступен? Хотя даже без него можно)
// в случае C++11
#include <type_traits>

template<typename T>
T OCCINumberToInt(const oracle::occi::Number &number, OCIError *hError)
{
    // тут можно нанаставлять статик ассертов для лимитации типов по размерам

    const OCINumber *ociNumber = reinterpret_cast<const OCINumber*>(&number);
    T result;
    // Если C++11
    //const static bool bSigned = std::is_signed<T>::value;
    // Если нет:
    const static bool bSigned = T(-1) < T(0);
    OCINumberToInt (hError, ociNumber, sizeof(T), bSigned ? OCI_NUMBER_SIGNED : OCI_NUMBER_UNSIGNED, &result);
    return result;
}


bSigned тут вычислится один раз для каждого типа.

тогда использовать можно так:
uint64_t unsignedValue64 = OCCINumberToInt<uint64_t>(ociNumber, err);
int64_t signedValue64 = OCCINumberToInt<int64_t>(ociNumber, err);

причём не только для 64бит, но внимательно посмотреть и статических ассертов добавить на допустимые длины.

кроме того, я бы не стал использова Си-стиль приведения типов, а использовал бы громкий reinterpret_cast<>().
вы тут сохраняете в знаковый int64 как знаковые, так и беззнаковые значения. Судя по всему тестировали только на положительных числах?

Справедливое замечание. В моей задаче полученный int64 сравнивается с другими int64 только на равно/не равно, а потом сохраняется в двоичном виде в файл, который читает другой модуль. Поэтому для меня имеет значение только как проведет операцию преобразования OCI — со знаком или без, а это я передаю ему параметром. Но для «библиотечной» функции ваш вариант, конечно, правильней.
причём не только для 64бит, но внимательно посмотреть и статических ассертов добавить на допустимые длины.

Для чисел меньших размеров необходимости в такой функции нет, т.к. сам Number имеет соответствующие операторы преобразования в 1-, 2- и 4-байтные знаковые и беззнаковые целые числа. Проблема именно в отсутствии поддержки 64-битных значений.
bSigned тут вычислится один раз для каждого типа.

А для чего вы определили ее вообще? Можно ведь написать так:

OCINumberToInt (hError, ociNumber, sizeof(T), T(-1) < T(0) ? OCI_NUMBER_SIGNED : OCI_NUMBER_UNSIGNED, &result);
А для чего вы определили ее вообще? Можно ведь написать так:

можно. Но для наглядности я бы вообще так сделал:
const static bool bSigned = T(-1) < T(0);
const static uword flags    = bSigned ? OCI_NUMBER_SIGNED : OCI_NUMBER_UNSIGNED;
// в случае C++11 можно и одной строчкой: из контекста и так понятно что происходит, а что такое T(-1) < T(0) сто пудов не сходу вспомнится:
//constexpr static uword flags = std::is_signed<T>::value ? OCI_NUMBER_SIGNED : OCI_NUMBER_UNSIGNED;
...
OCINumberToInt (hError, ociNumber, sizeof(T), flags, &result);


просто читабельность повыше. Оптимизаторы нынче умные. Можешь пометить как constexpr (C++11) и, я думаю, оно в компайл-тайме посчитается (а может и так соптимизируется). А код разбирать потом зело удобнее будет.
Only those users with full accounts are able to leave comments. Log in, please.

Articles