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

时间:2016-06-09 08:17:54

标签: java c++ c serialization

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

在:

上引用了序列化库

还有:

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

因此,如果我在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一样继承任何可序列化的C / C ++对象吗?

4 个答案:

答案 0 :(得分:13)

没有标准的库类以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<MyType> 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<MyType> 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

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

所有基本类型都预先定义了序列化插入 / 提取)运算符,但您仍需要注意{{ {1}}可以包含(例如)空格或换行符(如果使用空格或换行符作为字段分隔符)。

答案 1 :(得分:5)

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

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

    class SuperMan : public BaseObj
    {
    public:
        virtual void read(Stream& stream);
        virtual void write(Stream& stream);
    };
    
  • 类应该实现特殊的接口 - 在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);
    };
    
  • 库可能允许(或要求)注册&#34;序列化器&#34;对于给定的类型。它们可以通过从特殊基类或接口创建类,然后为给定类型注册它们来实现:

    #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());
    }
    
  • 序列化器也可以使用仿函数实现,例如lambda表达式:

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

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

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

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

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

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

答案 2 :(得分:3)

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

答案 3 :(得分:1)

幸运的是...... C ++没有对类层次结构进行序列化的默认机制。 (我不介意它提供标准库中的特殊基类型提供的可选机制,但总的来说这可能会限制现有的ABI)

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

您可能还希望查看here以了解要考虑的复杂性,如果您真的想根据标准进行验证purchase a copy here。看起来下一个标准的工作草案在github上。

特定应用系统

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

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

通常对于实时高性能游戏,您将明确禁用RTTI(说实话,它在C ++中并不是非常有用)甚至可能是例外(很多人不想要开销)在这里产生,如果你真的很疯狂,你可以通过跳远等方式实现你自己的形式。对我来说,异常会创建一个看不见的界面,经常会产生人们甚至不希望有可能出现的错误,所以我经常会避免它们支持更明确的逻辑。

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

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

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

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

语言层面的反思

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

陈述性方法

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

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

所以我通常会在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 )
{
}

然后我可以直接使用反射界面和一些方法,例如:

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

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

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。我希望尽快开源,所以如果您对这些细节感兴趣请告诉我。

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

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

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

C ++ Lax保证

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

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

(ptrType_t)&reinterpret_cast<ptrType_t&>((reinterpret_cast<CLASS*>(0))->member)

关于反射的一般警告

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

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

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

结论

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

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

如果您搜索&#34; C ++ Reflection&#34;您将看到其他人如何获得类似结果的几个例子。

快速搜索提升了this作为一个例子。