我有以下结构用于保存插件信息。我非常确定这会随着时间的推移而改变(最可能添加)。假设这个文件将被修复,这里有什么比我做的更好吗?
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;
};
此外,在为此系统构建共享对象时,是否应采取任何预防措施。我的预感是我会遇到很多库兼容性问题。请帮忙。感谢
答案 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_member
是PluginInfo
的新亮点,而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前面添加添加字段,相应地减小其大小,而不是修改/重新排列其他字段,那么您应该没问题。不需要任何魔法。较旧的软件不会触及新字段,因为它们不了解它们,内存占用量将保持不变。