2

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

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;
};

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

4

6 回答 6

6

这个怎么样,还是我想的太简单了?

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

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

于 2010-10-14T07:17:30.857 回答
6

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

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

但是期望二进制兼容性并使用 std::string 是非常罕见的。您将永远无法升级或更改您的编译器。

于 2010-10-14T07:35:39.507 回答
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*>.

于 2010-10-14T07:11:04.333 回答
2

一个可怕的想法:

一个std::map<std::string, std::string> m_otherKeyValuePairs;足以满足未来 500 年的需求。

编辑:

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

另一个同样可怕的想法:
a std::string m_everythingInAnXmlBlob;,就像在真实软件中看到的那样。

(可怕==不推荐)

编辑3:

  • 优点:
    成员std::map不受对象切片的影响。当较旧的源代码复制包含属性包中新键的 PluginInfo 对象时,会复制整个属性包。
  • 缺点:
    很多程序员会开始往属性包中添加不相关的东西,甚至开始编写处理属性包中值的代码,导致维护噩梦。
于 2010-10-14T07:03:26.093 回答
2

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

许多地方的 Windows API 通过将size成员放入结构中来保持二进制兼容性:

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在缓冲区中,这样该类的用户甚至不必担心这一点。


编辑:因为这似乎不像我想象的那么明确,这里有一个例子。
假设struct在更高版本的 API 中同样会获得一些额外的成员:

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),因此它将能够捕获它,并将指针视为指向不具有fancy_API_version2_member. (推测,宿主应用程序 API 的内部,PluginInfo是带有 , 的新的闪亮的fancy_API_version2_member,并且PluginInfoVersion1是旧类型的新名称。所以所有新的 API 需要做的就是将PluginInfo*它交给插件转换成一个PluginInfoVersion1*和分支到可以处理那个尘土飞扬的旧东西的代码。)

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

于 2010-10-14T09:46:05.097 回答
1

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

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

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

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

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

于 2010-10-14T07:33:55.750 回答