维护 ABI:向结构添加构造函数

发布于 2024-09-13 19:33:51 字数 827 浏览 11 评论 0原文

我们在共享库的修订版 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 的版本 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,这很好。任何重新编译的代码都将使用无参数构造函数。

我看到的唯一问题是,如果我们需要回滚到没有构造函数的旧版本共享库,那么针对它编译的任何代码都会中断,但我并不担心这种情况。

We have a struct in revision 1 of a shared library that we need to maintain the ABI for:

struct Person
{
    std::string first_name;
    std::string last_name;
}

In the revision 2, we're changing Person to this:

class Person
{
public:
    Person(const std::string &f, const std::string &l);

    std::string first_name;
    std::string last_name;
}

To maintain source compatibility, we'd like to modify reversion 1 of Person so that code compiled against newer header files will run and code not recompiled will run.

Can we do the following with two new non-inline constructors:

class Person
{
public:
    Person();
    Person(const std::string &f, const std::string &l);

    std::string first_name;
    std::string last_name;
}

We're doing this all with g++. In looking in the generated shared library with nm, I don't see a constructor or destructor for the plain struct, so I'm guessing that code that is not recompiled will just construct the Person at the calling site as before which is fine. Any code that is recompiled will use the no-arg constructor.

The only problem I see is if we need to roll back to an older version of the shared library that doesn't have the constructors, then any code compiled against it will break, but I'm not concerned about this situation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

旧梦荧光笔 2024-09-20 19:33:51

那么下面的呢?

class NewPerson : public Person
{
public:
    NewPerson(const std::string &f, const std::string &l)
    {
      first_name = f;
      last_name = l;
    }
}

What about the following?

class NewPerson : public Person
{
public:
    NewPerson(const std::string &f, const std::string &l)
    {
      first_name = f;
      last_name = l;
    }
}
绳情 2024-09-20 19:33:51

它可能“有效”,但您将违反“单一定义规则”,并且就 C++ 标准而言,您将陷入“未定义行为”领域,这不是一个好地方。

It might "work", but you will be breaking the One Definition Rule, and as far as the C++ Standard goes you will be off in Undefined Behaviour land, which is not a good place to be.

Smile简单爱 2024-09-20 19:33:51

我认为它应该有效,假设您的显式默认构造函数与之前使用的隐式构造函数执行相同的操作。在这个简单的例子中。然而,恕我直言,很难预测或知道编译器将做什么/改变什么。我自己不会相信它,如果我是你,我宁愿重新编译库用户。

I think that it should work, assuming that your explicit default ctor does the same thing as the previously used implicit ctor. In this simple example. However it is IMHO hard to predict or know what the compiler will do/change. I would not trust it myself, I would rather recompile the library users, if I were you.

仲春光 2024-09-20 19:33:51

在不破坏二进制兼容性的情况下向类或结构添加新的非虚拟函数应该没有问题。这是因为类函数被实现为普通函数,并采用隐式 this 作为其第一个参数。

但是,如果添加新的虚拟函数,则可能会破坏兼容性,因为新函数将强制修改 vtable,可能会破坏兼容性。

因此添加额外的构造函数(永远不能是虚拟的)不会破坏兼容性。如果要添加虚拟析构函数,很可能会破坏兼容性。

You should have no problem adding new non-virtual functions to a class or struct without breaking binary compatibility. This is because a class function is implemented as a normal function taking an implicit this as its first parameter.

If you add a new virtual function however, you may break compatibility since the new function will force the vtable to be modified potentially breaking compatibility.

So adding extra constructors (which can never be virtual) will not break compatibility. If you were to add a virtual destructor, you will most probably break compatibility.

|煩躁 2024-09-20 19:33:51

此类事情可能存在风险,了解何时可以或不能安全地进行此类更改可能很困难。该标准不会帮助您,它只是将任何此类更改称为“未定义的行为”。

g++ 确实有一个定义良好的 ABI,但该 ABI 相当复杂,并且有一些您可能不知道的极端情况。

一个特别值得关注的是 POD 和非 POD 类型的处理方式通常截然不同。将构造函数添加到以前是 POD 的类型可以使其成为非 POD,这可以显着改变它作为参数传递的方式以及它作为基类合并的方式。在您的特定情况下,由于字符串字段,您的类型已经是非 POD,所以我认为这在这种特定情况下不是问题,但它肯定是您在类似情况下可能遇到的陷阱。

还有一个有趣的问题是,当某些行为根据 ABI 明确定义,但根据 C++ 标准未定义时,会发生什么情况。如果禁用 lto,这不是问题,但如果启用 lto,编译器有可能检测到未定义的行为并将其视为优化机会。我不知道是否真的有。

This sort of thing can be risky, knowing when you can and cannot safely make such a change can be difficult. The standard won't help you, it just calls any such change "undefined behaviour".

g++ does have a well-defined ABI, but that ABI is pretty complex and has some corner cases you may not be aware of.

A particular concern is that POD and non-POD types are often handled quite differently. Adding a constructor to a type that was previously POD can make it non-POD which can significantly change how it is passed as a parameter and how it is incorporated as a base-class. In your particular case your type is already non-POD because of the string fields, so I don't think this is an issue in this particular case but it's certainly a trap you could run into in similar cases.

There is also the interesting question of what happens when the behavior of something is well-defined per the ABI, but undefined per the C++ standard. If lto is disabled this is no issue, but if lto is enabled there is the potential for the compiler to detect the undefined behavior and treat it as an optimization opportunity. Whether any actually do I have no idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文