我们在共享库的修订版1中有一个结构,我们需要维护ABI:
struct Person
{
std::string first_name;
std::string last_name;
}
在修订版2中,我们将Person更改为:
class Person
{
public:
Person(const std::string &f, const std::string &l);
std::string first_name;
std::string last_name;
}
为了保持源兼容性,我们想修改Person的reversion 1,以便运行针对较新头文件编译的代码并运行未重新编译的代码。
我们可以使用两个新的非内联构造函数来执行以下操作:
class Person
{
public:
Person();
Person(const std::string &f, const std::string &l);
std::string first_name;
std::string last_name;
}
我们用g ++完成所有这些。在使用nm查看生成的共享库时,我没有看到普通结构的构造函数或析构函数,所以我猜测未重新编译的代码只会像以前一样构建调用站点上的Person,这很好。任何重新编译的代码都将使用no-arg构造函数。
我看到的唯一问题是,如果我们需要回滚到没有构造函数的旧版本的共享库,那么针对它编译的任何代码都会中断,但我不关心这种情况。
答案 0 :(得分:3)
以下怎么样?
class NewPerson : public Person
{
public:
NewPerson(const std::string &f, const std::string &l)
{
first_name = f;
last_name = l;
}
}
答案 1 :(得分:2)
我认为它应该有效,假设你的显式默认ctor与之前使用的隐式ctor做同样的事情。在这个简单的例子中。然而,恕我直言,很难预测或知道编译器将做什么/改变什么。我不相信自己,如果我是你,我宁愿重新编译图书馆用户。
答案 2 :(得分:2)
它可能“有效”,但你将打破一个定义规则,并且就C ++标准来说,你将在Undefined Behavior土地上离开,这不是一个好地方。
答案 3 :(得分:1)
在不破坏二进制兼容性的情况下,将新的非虚函数添加到类或结构中应该没有问题。这是因为类函数被实现为以隐式this
作为其第一个参数的正常函数。
但是,如果添加新的虚函数,则可能会破坏兼容性,因为新函数会强制修改vtable,从而可能会破坏兼容性。
因此添加额外的构造函数(永远不会是虚拟的)不会破坏兼容性。如果你要添加一个虚拟析构函数,你很可能会破坏兼容性。
答案 4 :(得分:0)
这类事情可能是冒险的,要知道何时可以安全地进行这样的更改可能很困难。该标准将无济于事,只是将任何此类更改称为“未定义的行为”。
g ++确实具有定义明确的ABI,但该ABI非常复杂,并且有一些您可能不知道的极端情况。
一个特别令人担忧的问题是,POD和非POD类型的处理方式通常完全不同。将构造函数添加到以前是POD的类型可以使它成为非POD,这可以显着改变将其作为参数传递的方式以及将其作为基类合并的方式。在特定情况下,由于字符串字段的原因,您的类型已经是非POD了,所以我认为在这种情况下这不是问题,但是在类似情况下肯定会遇到陷阱。
还有一个有趣的问题,当根据ABI明确定义某事物的行为,而根据C ++标准却未定义行为时,会发生什么。如果禁用了lto,则没有问题,但是,如果启用了lto,则编译器有可能检测未定义的行为并将其视为优化机会。我是否真的不知道。