有没有办法为未来的添加准备一个结构?

时间:2010-10-14 06:56:22

标签: c++ struct shared-objects

我有以下结构用于保存插件信息。我非常确定这会随着时间的推移而改变(最可能添加)。假设这个文件将被修复,这里有什么比我做的更好吗?

struct PluginInfo
{
    public:
        std::string s_Author;
        std::string s_Process;
        std::string s_ReleaseDate;
        //And so on...

        struct PluginVersion
        {
            public:
                std::string s_MajorVersion;
                std::string s_MinorVersion;
                //And so on...
        };
        PluginVersion o_Version;

        //For things we aren't prepared for yet.
        void* p_Future;
};

此外,在为此系统构建共享对象时,是否应采取任何预防措施。我的预感是我会遇到很多库兼容性问题。请帮忙。感谢

6 个答案:

答案 0 :(得分:6)

这个怎么样,还是我觉得太简单了?

struct PluginInfo2: public PluginInfo
{
    public:
        std::string s_License;
};

在您的应用程序中,您可能只传递指向PluginInfo的指针,因此版本2与版本1兼容。当您需要访问版本2成员时,您可以使用{{1}测试版本或者使用明确的dynamic_cast<PluginInfo2 *>成员。

答案 1 :(得分:6)

您的插件是使用相同版本的C ++编译器和std库源编译的(或者它的std :: string实现可能不兼容,并且所有字符串字段都会中断),在这种情况下您必须重新编译插件无论如何,向结构添加字段无关紧要

或者你想要与以前的插件二进制兼容,在这种情况下坚持使用普通数据和固定大小的char数组(或提供一个API来根据大小为字符串分配内存或传入一个const char *),其中在结构中有一些未使用的字段并不是闻所未闻,然后在需要时将它们更改为有用的命名项。在这种情况下,在结构中有一个字段来说明它代表的版本也很常见。

但是很少期望二进制兼容性并使用std :: string。您永远无法升级或更改编译器。

答案 2 :(得分:2)

一个可怕的想法:

未来500年,std::map<std::string, std::string> m_otherKeyValuePairs;就足够了。

修改

另一方面,这个建议很容易被滥用,因此可能有资格获得TDWTF。

另一个同样可怕的想法:
a std::string m_everythingInAnXmlBlob;,如真实软件中所示。

(hideous ==不推荐)

编辑3:

  • 优势:
    std::map会员不受object slicing的约束。当较旧的源代码复制包含属性包中的新键的PluginInfo对象时,将复制整个属性包。
  • 缺点:许多程序员会开始在属性包中添加不相关的东西,甚至开始编写处理属性包中的值的代码,导致维护噩梦。

答案 3 :(得分:2)

rwong建议(std::map<std::string, std::string>)是一个好方向。这样可以添加有意的字符串字段。如果您想要更灵活,可以声明一个抽象基类

class AbstractPluginInfoElement { public: virtual std::string toString() = 0;};

class StringPluginInfoElement : public AbstractPluginInfoElement 
{ 
  std::string m_value;
  public: 
   StringPluginInfoElement (std::string value) { m_value = value; }
   virtual std::string toString() { return m_value;}
};

然后,您可能会派生出更复杂的类,如PluginVersion等,并存储map<std::string, AbstractPluginInfoElement*>

答案 4 :(得分:2)

正如其他人所说,对于二进制兼容性,您很可能会将自己局限于C API。

许多地方的Windows API通过将size成员放入struct:

来维护二进制兼容性
struct PluginInfo
{
    std::size_t size; // should be sizeof(PluginInfo)

    const char* s_Author;
    const char* s_Process;
    const char* s_ReleaseDate;
    //And so on...

    struct PluginVersion
    {
        const char* s_MajorVersion;
        const char* s_MinorVersion;
        //And so on...
    };
    PluginVersion o_Version;
};

当你创建这样的野兽时,你需要相应地设置size成员:

PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members

当您针对较新版本的API编译代码时,struct具有其他成员,其大小会发生变化,并且会在其size成员中注明。 API函数在被传递时,struct可能会首先读取其size成员并以不同的方式处理struct,具体取决于其大小。

当然,这假设进化是线性的,并且新数据总是仅在struct的末尾添加。也就是说,你永远不会有这种类型的不同版本具有相同的大小。


然而,使用这样的野兽是确保用户在其代码中引入错误的好方法。当他们针对新API重新编译代码时,sizeof(pluginInfo)将自动适应,但不会自动设置其他成员。通过“初始化”struct C方式可以获得相当的安全性:

PluginInfo pluginInfo;
std::memset( &pluginInfo, 0, sizeof(pluginInfo) );
pluginInfo.size = sizeof(pluginInfo);

然而,即使抛弃这样一个事实,即从技术上讲,将内存归零可能无法为每个成员提供合理的值(例如,可能存在所有位设置为零的架构都不是浮点类型的有效值),这很烦人且容易出错,因为它需要三步构造。

一种解决方法是围绕该C API设计一个小型的内联C ++包装器。类似的东西:

class CPPPluginInfo : PluginInfo {
public:
  CPPPluginInfo()
   : PluginInfo() // initializes all values to 0
  {
    size = sizeof(PluginInfo);
  }

  CPPPluginInfo(const char* author /* other data */)
   : PluginInfo() // initializes all values to 0
  {
    size = sizeof(PluginInfo);
    s_Author = author;
    // set other data
  }
};

该类甚至可以将C struct成员指向的字符串存储在缓冲区中,这样该类的用户甚至不必担心这一点。


编辑 因为它看起来并不像我想象的那么明确,这是一个例子。
假设在更高版本的API中,非常相同的struct将获得一些额外的成员:

struct PluginInfo
{
    std::size_t size; // should be sizeof(PluginInfo)

    const char* s_Author;
    const char* s_Process;
    const char* s_ReleaseDate;
    //And so on...

    struct PluginVersion
    {
        const char* s_MajorVersion;
        const char* s_MinorVersion;
        //And so on...
    };
    PluginVersion o_Version;

    int fancy_API_version2_member;
};

当链接到旧版API的插件现在初始化其struct

PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members

struct将是旧版本,缺少API版本2中新的闪亮数据成员。如果它现在调用第二个API的函数接受指向PluginInfo的指针,它会将旧的PluginInfo的地址(简称一个数据成员)传递给新API的函数。但是,对于版本2 API函数,pluginInfo->size将小于sizeof(PluginInfo),因此它可以捕获它,并将指针视为指向不具有{{1的对象}}。 (据推测,主机应用的API内部,fancy_API_version2_memberPluginInfo的新亮点,而fancy_API_version2_member是旧类型的新名称。所以所有新的API都需要要做的就是将它所拥有的PluginInfoVersion1转换为PluginInfo*并分支到可以处理那个尘土飞扬的旧事物的代码。)

另一种方法是针对新版API编译的插件,其中PluginInfoVersion1*包含PluginInfo,插入到对它一无所知的主机应用程序的旧版本中。同样,主机应用程序的API函数可以通过检查fancy_API_version2_member是否更大而不是pluginInfo->size他们自己的sizeof来捕获它。如果是这样,该插件可能是针对比主机应用程序知道的更新版本的API编译的。 (或者插件写入无法正确初始化PluginInfo成员。请参阅下文,了解如何简化处理这种有点脆弱的方案。)
有两种方法可以解决这个问题:最简单的方法就是拒绝加载插件。或者,如果可能的话,主机应用程序无论如何都可以使用它,只是忽略它传递的size对象末尾的二进制内容,它不知道如何解释。
但是,后者很棘手,因为在实现旧API 时需要确定,而不确切知道新API的外观。

答案 5 :(得分:1)

这是一个想法,不确定它是否适用于类,它肯定适用于结构:你可以使结构“保留”一些空间,以便将来使用如下:

struct Foo
{
  // Instance variables here.
  int bar;

  char _reserved[128]; // Make the class 128 bytes bigger.
}

初始化程序会在填充之前将整个结构清零,因此访问现在位于“保留”区域内的字段的类的较新版本具有合理的默认值。

如果您只在_reserved前面添加添加字段,相应地减小其大小,而不是修改/重新排列其他字段,那么您应该没问题。不需要任何魔法。较旧的软件不会触及新字段,因为它们不了解它们,内存占用量将保持不变。