如何正確讀取 RTI 中的 enum
背景
假設錄製的 RTI 資料儲存.dat 檔案中,使用 DB browser for SQLite 檢視.dat 檔案,其中包含了要讀取的 topic 資料,這個 topic 對應的 IDL 中含義 enum 型別,假設其定義如下:
module Sample {
enum State {
UNKNOWN = -1,
MANUAL = 1,
STARTED = 2,
READY = 3
};
struct Info {
Header header;
State state;
string name;
boolean valid;
};
};
之後使用 RTI 的介面從.dat 中讀取了資料,返回的資料型別為dds::core::xtypes::DynamicData dynamic_raw
,然後從中讀取需要的欄位,將其儲存起來。
對於其他型別的資料,例如string
或者boolean
,可以透過下面的方式讀取:
inline void SampleReader::FillData(dds::core::xtypes::DynamicData dynamic_raw) {
if (dynamic_raw.member_exists("name")) {
idl_message_.name(dynamic_raw.value<std::string>("name"));
}
if (dynamic_raw.member_exists("valid")) {
idl_message_.valid(dynamic_raw.value<bool>("valid"));
}
}
但是對於enum
型別的資料,直接透過value
讀取是不行的,
inline void SampleReader::FillData(dds::core::xtypes::DynamicData dynamic_raw) {
if (dynamic_raw.member_exists("state")) {
idl_message_.state(dynamic_raw.value<Sample::State>("state"));
}
}
這樣做會提示未定義符號,例如:
undefined symbol: _ZNK3rti4core6xtypes15DynamicDataImpl5valueIN3dds4core9safe_enumIN15Sample8State_defENS9_4typeEEEEET_RKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
這個原因比較奇怪,實際上這個型別Sample::State
被匯出了,idl_message_
就是Sample::State
型別的,可在讀取的時候就不能這麼使用。考慮到 enum 可能是一個 int 型別,所以可以透過下面的方式讀取:
auto state{dynamic_raw.value<int32_t>("state")};
idl_message_.state(static_cast<Sample::State>(state));
這樣也不行,編譯都過不了,報錯為:
error: invalid conversion from ‘int’ to ‘dds::core::safe_enum<Sample::State_def>::inner_enum’ {aka ‘Sample::State_def::type’} [-fpermissive]
也就是不能將int
型別直接轉換為Sample::State
型別。那麼應該如何正確讀取 DDS 中的 enum 呢?
解決方案
檢視原始碼,發現列舉Sample::State
的定義為
struct State_def {
enum type {
UNKNOWN = -1,
MANUAL = 1,
STARTED = 2,
READY = 3
};
static type get_default(){ return UNKNOWN;}
};
typedef ::dds::core::safe_enum< State_def > State;
NDDSUSERDllExport std::ostream& operator << (std::ostream& o,const State& sample);
即Sample::State
實際是dds::core::safe_enum
的例項化,而safe_enum
是一個模版類,其定義如下:
template<typename def, typename inner = typename def::type>
class UserDllExport safe_enum : public def {
public:
/**
* The underlying \p enum type
*
* To convert from an integer, \p static_cast it to the member type \p inner_enum:
*
* \code
* int reliability_as_int = 2;
* ReliabilityKind reliability = static_cast<ReliabilityKind::inner_enum>(reliability_as_int);
* \endcode
*/
typedef inner inner_enum;
public:
/**
* @brief Initializes the enumeration to zero
*
* @note Zero may not be a valid enumerator. It's best to assign a valid
* enumerator whenever possible:
* \code
* Reliability reliability = Reliability::RELIABLE;
* \endcode
*/
safe_enum() : val(static_cast<inner>(0))
{
}
/**
* @brief Copy constructor
*/
safe_enum(inner_enum v) : val(v) {}
/**
* @brief Retrieves the actual \p enum value
*/
inner_enum underlying() const { return val; }
private:
inner_enum val;
};
inner_enum
的註釋中提到,可以透過static_cast
將int
轉換為inner_enum
,所以可以透過下面的方式讀取:
inline void SampleReader::FillData(dds::core::xtypes::DynamicData dynamic_raw) {
if (dynamic_raw.member_exists("state")) {
auto state{dynamic_raw.value<int32_t>("state")};
idl_message_.state(static_cast<Sample::State::inner_enum>(state));
}
}
原來解決方案就在原始碼裡面……