开发者

Maintaining ABI: adding constructor to struct

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 c开发者_JS百科alling 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.


What about the following?

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


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.


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜