什么是java.io.Serializable的C / C ++等价?

什么是java.io.Serializable的C / C ++等价?

有关序列化库的参考:

  • 用C语言序列化数据结构

还有:

  • http://troydhanson.github.io/tpl/index.html
  • http://www.boost.org/doc/libs/1_41_0/libs/serialization/doc/index.html
  • https://developers.google.com/protocol-buffers/docs/cpptutorial#optimization-tips

但这样的等价甚至存在吗?

因此,如果我在Java中有如下的抽象类,那么C / C ++中的可序列化类如何?

import java.io.Serializable; public interface SuperMan extends Serializable{ /** * Count the number of abilities. * @return */ public int countAbility(); /** * Get the ability with index k. * @param k * @return */ public long getAbility(int k); /** * Get the array of ability from his hand. * @param k * @return */ public int[] getAbilityFromHand(int k); /** * Get the finger of the hand. * @param k * @return */ public int[][] getAbilityFromFinger(int k); //check whether the finger with index k is removed. public boolean hasFingerRemoved(int k); /** * Remove the finger with index k. * @param k */ public void removeFinger(int k); } 

可以像Java一样inheritance任何可序列化的C / C ++对象吗?

没有像Java那样实现序列化的标准库类。 有一些库可以促进序列化,但是对于基本需求,您通常可以通过重载插入提取操作符来使类可序列化 ,如下所示:

 class MyType { int value; double factor; std::string type; public: MyType() : value(0), factor(0.0), type("none") {} MyType(int value, double factor, const std::string& type) : value(value), factor(factor), type(type) {} // Serialized output friend std::ostream& operator<<(std::ostream& os, const MyType& m) { return os << m.value << ' ' << m.factor << ' ' << m.type; } // Serialized input friend std::istream& operator>>(std::istream& is, MyType& m) { return is >> m.value >> m.factor >> m.type; } }; int main() { std::vector v {{1, 2.7, "one"}, {4, 5.1, "two"}, {3, 0.6, "three"}}; std::cout << "Serialize to standard output." << '\n'; for(auto const& m: v) std::cout << m << '\n'; std::cout << "\nSerialize to a string." << '\n'; std::stringstream ss; for(auto const& m: v) ss << m << '\n'; std::cout << ss.str() << '\n'; std::cout << "Deserialize from a string." << '\n'; std::vector v2; MyType m; while(ss >> m) v2.push_back(m); for(auto const& m: v2) std::cout << m << '\n'; } 

输出:

 Serialize to standard output. 1 2.7 one 4 5.1 two 3 0.6 three Serialize to a string. 1 2.7 one 4 5.1 two 3 0.6 three Deserialize from a string. 1 2.7 one 4 5.1 two 3 0.6 three 

序列化格式完全取决于程序员,您负责确保要序列化的类的每个成员本身是可序列化的 (已定义插入 / 提取操作符)。 您还必须处理字段如何分隔(空格或换行或零终止?)。

所有基本类型都预先定义了序列化插入 / 提取 )运算符,但是你仍然需要小心std::string类的东西,它们可以包含(例如)空格或换行符(如果你使用空格或新的 -行作为字段分隔符)。

没有一个标准。 事实上,每个库都可以以不同的方式实现它。 以下是一些可以使用的方法:

  • class必须从公共基类派生,并实现read()write()虚方法:

     class SuperMan : public BaseObj { public: virtual void read(Stream& stream); virtual void write(Stream& stream); }; 
  • class应该实现特殊的接口 – 在C ++中,这是通过从特殊的抽象类派生类来完成的。 这是以前方法的变种:

     class Serializable { public: virtual Serializable() {} virtual void read(Stream& stream) = 0; virtual void write(Stream& stream) = 0; }; class SuperMan : public Man, public Serializable { public: virtual void read(Stream& stream); virtual void write(Stream& stream); }; 
  • 库可以允许(或要求)为给定类型注册“序列化器”。 它们可以通过从特殊基类或接口创建类,然后为给定类型注册它们来实现:

     #define SUPERMAN_CLASS_ID 111 class SuperMan { public: virtual int getClassId() { return SUPERMAN_CLASS_ID; } }; class SuperManSerializer : public Serializer { virtual void* read(Stream& stream); virtual void write(Stream& stream, void* object); }; int main() { register_class_serializer(SUPERMAN_CLASS_ID, new SuperManSerializer()); } 
  • 序列化器也可以使用仿函数实现,例如lambdas:

     int main { register_class_serializer(SUPERMAN_CLASS_ID, [](Stream&, const SuperMan&) {}, [](Stream&) -> SuperMan {}); } 
  • 而不是将序列化器对象传递给某个函数,将其类型传递给特殊模板函数可能就足够了:

     int main { register_class_serializer(); } 
  • class应该提供像’<<'和'>>’这样的重载运算符。 它们的第一个参数是一些流类,第二个参数是类实例。 Stream可以是std::stream ,但这会导致与这些运算符的默认使用冲突 – 转换为用户友好的文本格式。 因为这个流类是专用的(它可以包装std :: stream),或者如果必须支持<< ,则库将支持替代方法。

     class SuperMan { public: friend Stream& operator>>(const SuperMan&); friend Stream& operator<<(const SuperMan&); }; 
  • 我们的类类型应该有一些类模板的特化。 此解决方案可以与<<>>运算符一起使用 - 库首先会尝试使用此模板,如果不是专门的,则还原为运算符(这可以作为默认模板版本实现,或使用SFINAE)

     // default implementation template class Serializable { public: void read(Stream& stream, const T& val) { stream >> val; } void write(Stream& stream, const T& val) { stream << val; } }; // specialization for given class template<> class Serializable { void read(Stream& stream, const SuperMan& val); void write(Stream& stream, const SuperMan& val); } 
  • 而不是类模板库也可以使用带有全局重载函数的C风格接口:

     template void read(Stream& stream, const T& val); template void write(Stream& stream, const T& val); template<> void read(Stream& stream, const SuperMan& val); template<> void write(Stream& stream, const SuperMan& val); 

C ++语言很灵活,所以上面的列表肯定是不完整的。 我相信有可能发明另一种解决方案。

正如其他答案所提到的,C ++几乎没有Java(或其他托管语言)所具有的内置序列化/反序列化function。 这部分是由于C ++中可用的最小运行时类型信息(RTTI)。 C ++本身没有reflection,因此每个可序列化对象必须完全负责序列化。 在Java和C#等托管语言中,该语言包含足够的RTTI,外部类能够枚举对象上的公共字段以执行序列化。

幸运的是…… C ++没有强制使用类层次结构序列化的默认机制。 (我不介意它提供标准库中的特殊基类型提供的可选机制或其他东西,但总的来说这可能会限制现有的ABI)

序列化在现代软件工程中非常重要和强大。 每当我需要将类层次结构转换为某种forms的运行时可消耗数据时,我就会使用它。 我总是选择的机制是基于某种forms的反思。 更多关于此的信息。

您可能还想在这里查看要考虑的复杂性的想法,如果您真的想要validation标准,您可以在此处购买副本 。 看起来下一个标准的工作草案是在github上 。

应用特定系统

C ++ / C允许应用程序的作者自由选择许多技术背后的机制,人们认为这些技术是新的,通常是更高级的语言。 reflection( RTTI ),例外,资源/内存管理(垃圾收集, RAII等)。 这些系统都可能影响特定产品的整体质量。

我从事过各种工作,从实时游戏,嵌入式设备,移动应用程序到Web应用程序,特定项目的总体目标各不相同。

通常对于实时高性能游戏,你会明确地禁用RTTI (说实话它在C ++中并不是很有用)甚至可能是例外(许多人不希望这里产生的开销,如果你真的很疯狂,你可以从跳远等方面实现你自己的forms。对我来说,例外创造了一个看不见的界面,经常会产生人们甚至不可能想到的错误,所以我常常避免使用它们而不是更明确的逻辑。)

垃圾收集在默认情况下不包含在C ++中,在实时游戏中这是一种祝福。 当然,你可以使用增量GC和其他优化方法,我已经看过许多游戏使用(通常它是对现有GC的修改,就像Mono for C#中使用的那样)。 许多游戏都使用池,通常用于智能指针驱动的C ++ RAII 。 具有不同存储器使用模式的不同系统可以以不同方式优化,这并不罕见。 关键是一些应用程序更关注其他关于细节的细节。

类型层次结构自动序列化的一般思路

类型层次结构的自动序列化系统的一般思想是使用可以从通用接口在运行时查询类型信息的reflection系统。 我的解决方案依赖于通过在宏的帮助下扩展一些基本类型接口来构建该通用接口。 最后,您基本上可以获得一个动态vtable的排序,您可以通过索引或查询按成员/类型的字符串名称进行迭代。

我还使用了一个基本reflection读取器/写入器类型,它公开了一些iostream接口,以允许派生格式化程序覆盖。 我目前有一个BinaryObjectIO,JSONObjectIO和ASTObjectIO,但添加其他内容是微不足道的。 这样做的目的是从层次结构中负责地删除序列化特定数据格式并将其放入序列化程序中。

在语言层面的反思

在许多情况下,应用程序知道它要序列化的数据,并且没有理由将其构建到该语言中的每个对象中。 许多现代语言包括RTTI,即使在系统的基本类型中(如果它们是基于类型的常见内在函数将是int,float,double等)。 这需要为系统中的所有内容存储额外的数据,而不管应用程序的使用情况如何。 我相信很多现代编译器有时可以通过树摇动来优化一些,但是你也不能保证。

陈述性方法

已经提到的方法都是有效的用例,尽管它们通过让层次结构处理实际的序列化任务而缺乏一些灵活性。 这也可以通过层次结构上的样板流操作来膨胀您的代码。

我个人更喜欢通过反思采用更具声明性的方法。 我在过去所做的并且在某些情况下继续做的是在我的系统中创建一个基本的Reflectable类型。 我最终使用模板元编程来帮助一些样板逻辑以及字符串连接宏的预处理器。 最终结果是我派生的基类型,用于公开接口的可反映宏声明和用于实现guts的可反映宏定义(将注册成员添加到类型的查找表中的任务)。

所以我通常会在h中看到这样的东西:

 class ASTNode : public Reflectable { ... public: DECLARE_CLASS DECLARE_MEMBER(mLine,int) DECLARE_MEMBER(mColumn,int) ... }; 

然后在cpp中这样的事情:

 BEGIN_REGISTER_CLASS(ASTNode,Reflectable); REGISTER_MEMBER(ASTNode,mLine); REGISTER_MEMBER(ASTNode,mColumn); END_REGISTER_CLASS(ASTNode); ASTNode::ASTNode() : mLine( 0 ) , mColumn( 0 ) { } 

然后我可以直接使用reflection接口和一些方法,例如:

 int id = myreflectedObject.Get("mID"); myreflectedObject.Set( "mID", 6 ); 

但更常见的是,我只是迭代一些我用另一个界面公开的“Traits”数据:

 ReflectionInfo::RefTraitsList::const_iterator it = info->getReflectionTraits().begin(); 

目前traits对象看起来像这样:

 class ReflectionTraits { public: ReflectionTraits( const uint8_t& type, const uint8_t& arrayType, const char* name, const ptrType_t& offset ); std::string getName() const{ return mName; } ptrType_t getOffset() const{ return mOffset; } uint8_t getType() const{ return mType; } uint8_t getArrayType() const{ return mArrayType; } private: std::string mName; ptrType_t mOffset; uint8_t mType; uint8_t mArrayType; // if mType == TYPE_ARRAY this will give the type of the underlying data in the array }; 

我实际上已经对我的宏进行了改进,这使我能够简化这一点……但这些都取自我目前正在处理的实际项目。 我正在使用Flex,Bison和LLVM开发一种编程语言,它编译为C ABI和webassembly。 我希望尽快开源,所以如果你对细节感兴趣,请告诉我。

这里需要注意的是,“Traits”信息是可在运行时访问并描述该成员的元数据,并且通常对于一般语言级别reflection而言要大得多。 我在这里提供的信息是我所能提供的可反映类型。

序列化任何数据时要记住的另一个重要方面是版本信息。 上述方法将对数据进行反序列化,直到您开始更改内部数据结构。 但是,您可以在序列化系统中包含一个post和可能的预数据序列化挂钩机制,以便您可以修复数据以符合较新版本的类型。 我已经用这样的设置完成了几次这样的工作并且效果非常好。

关于这种技术的最后一点注意事项是,您明确控制了此处序列化的内容。 您可以选择要序列化的数据以及可能仅跟踪某些瞬态对象状态的数据。

C ++ Lax保证

有一点需要注意……因为C ++对于实际看起来是什么样的数据非常不严格。 您经常需要做出一些特定于平台的选择(这可能是未提供标准系统的主要原因之一)。 实际上,你可以在编译时使用模板元编程来做很多事情,但有时候假设你的char长度为8位更容易。 是的,即使这个简单的假设在C ++中也不是100%普遍的,幸运的是在大多数情况下都是如此。

我使用的方法也做了一些非标准的NULL指针转换来确定内存布局(再次为了我的目的,这是野兽的本质)。 以下是一个宏实现的示例片段,用于计算宏提供CLASS的类型中的成员偏移量。

 (ptrType_t)&reinterpret_cast((reinterpret_cast(0))->member) 

关于反思的一般警告

反思的最大问题是它有多强大。 您可以快速将易于维护的代码库转换为过多的不一致的reflection使用。

我个人保留对较低级别系统(主要是序列化)的reflection,并避免将其用于业务逻辑的运行时类型检查。 使用诸如虚函数之类的语言结构进行动态调度应优先于reflection类型检查条件跳转。

如果语言inheritance了对reflection的全部或全部支持,那么问题甚至更难追查。 例如,在C#中,您无法保证,在给定随机代码库的情况下,仅仅通过允许编译器提醒您任何用法而不使用函数。 您不仅可以通过代码库中的字符串调用该方法,也可以从网络数据包中调用该方法…您还可以破坏反映在目标程序集上的其他一些无关组件的ABI兼容性。 因此,再次使用reflection一致且谨慎。

结论

目前没有标准等效于C ++中可序列化类层次结构的通用范例,但它可以像您在较新语言中看到的任何其他系统一样添加。 毕竟,所有事情最终都转化为简单的机器代码,可以用CPU芯片中包含的令人难以置信的晶体管arrays的二进制状态来表示。

我并不是说每个人都应该以任何方式在这里推销自己。 这是一项复杂且容易出错的工作。 我真的很喜欢这个想法,现在一直对这种事感兴趣。 我确信人们会使用这些工作的标准后备。 如上所述,第一个寻找C ++的地方就是提升 。

如果您搜索“C ++ Reflection”,您将看到其他人如何获得类似结果的几个示例。

快速搜索将此作为一个例子。