开发者

C++ -- When recompilation is required

You have a class that many libraries depend on. You need to modify the class for one application. Which of the following changes require recompiling all libraries before it is safe to build the application?

  • add a constructor
  • add a data member
  • change destructor into virtual
  • add an argument开发者_JAVA百科 with default value to an existing member function


Classes are defined in the header file. The header file will be compiled into both the library that implements the class and the code that uses the class. I am assuming that you are taking as a given that you will need to recompile the class implementation after changing the class header file and that the question you are asking is whether you will need to recompile any code that references the class.

The problem that you are describing is one of binary compatibility (BC) and generally follows the following rules:

  1. Adding non-virtual functions anywhere in the class does not break BC.
  2. Changing any function definition (adding parameters )will break BC.
  3. Adding virtual functions anywhere changes the v-table and therefore breaks BC.
  4. Adding data members will break BC.
  5. Changing a parameter from non-default to default will not break BC.
  6. Any changes to inline functions will break BC (inline function should therefore be avoided if BC is important.)
  7. Changing compiler (or sometimes even compiler versions) will probably break BC unless the compilers adhere strickly to the same ABI.

If BC is a major issue for the platform you are implementing it could well be a good idea to separate out the interface and implementation using the Bridge pattern.

As an aside, the C++ language does not deal with the Application Binary Interface (ABI). If binary compatibility is a major issue, you should probably refer to your platform's ABI specification for more details.

Edit: updated adding data members. This will break BC because more memory will now be needed for the class than before.


Strictly speaking, you end up in Undefined Behavior land as soon as you do not recompile for any of those reasons.

That said, in practice you might get away with a few of them:

  • add a constructor

Might be Ok to use as long as

  1. it's not the first user-defined constructor to the class
  2. it's not the copy constructor
  • add a data member

This changes the size of instances of the class. Might be Ok for anyone who just uses pointers or references, if you take care to put that data behind all other data, so that the offsets for accessing the other data members do not change. But the exact layout of sub objects in binary is not defined, so you will have to rely on a specific implementation.

  • change destructor into virtual

This changes the class' virtual table, so it needs recompilation.

  • add an argument with default value to an existing member function

Since default arguments are inserted at the call site, everyone using this needs to recompile. (However, using overloading instead of default arguments might allow you to get away with that.)

Note that any inlined member function could render any of the above wrong, since the code of those is directly embedded (and optimized) in the clients' code.

However, the safest bet would be to just recompile everything. Why is this an issue?


All of them need to recompile all the libraries that use the class. (provided they include the .h file)


sbi's answer is pretty good (and deserves to be voted up to top). However I think I can expand the "maybe ok" into something more concrete.

  • Add a constructor

    If the constructor you've added is the default constructor (or indeed a copy constructor) then you have to be careful. If previously not available then they will have been automatically generated by the compiler (as such a recompilation is required to ensure they are using the actual constructor that has been implemented). For this reason I tend to always hide or define these constructors for classes that form some API.


By using ordinal export .def file to maintain Application Binary Interface, you can avoid client recompilation in many cases:

  • Add a constructor

    Export this constructor function to end of export table with largest ordinal number. Any client code doesn't call this constructor need not compile.

  • Add a data member

    This is a break if client code manipulates class object directly, not through pointer or reference.

  • Change destructor into virtual

    This is probably a break, if your class doesn't have any other virtual function, which means now your class has to add a vptr table and increase class object size and change memory layour. If your class has already have a vptr table, moving destructor to end of vptr table won't affect object layout in terms of backward compatibility. But if client class is derived from your class and has defined its own virtual function then it breaks. And also any client calling original non-virtual destructor will break.

  • Add an argument with default value to an existing member function

    This is definitely a break.


I am clearly against @sbi answer: in general you do need to recompile. Only under much more strict circumstances than the ones he posted you may get away.

  • add a constructor

If the constructor added is either the default constructor or the copy constructor, any code that used the implicitly defined version of it and does not get recompiled will fail to initialize the object, and that means that invariants required by other methods will not be set at construction, i.e. the code will fail.

  • add a data member

This modifies the layout of the object. Even code that only used pointers or references need to be recompiled to adapt to the change in layout. If a member is added at the beginning of the object, any code that used any member of the object will be offset and fail.

struct test { 
   // int x; // added later
   int y;
};
void foo( test * t ) {
   std::cout << t->y << std::endl;
}

If foo was not recompiled, then after uncommenting x it would print t->x instead of t->y. If the types did not match it would even be worse. Theoretically, even if the added member is at the end of the object, if there are more than one access modifier the compiler is allowed to reorder members and hit the same issue.

  • change destructor to virtual

If it is the first virtual method it will change the layout of the object and get all of the previous issues plus the addition that deleting through a reference to the base will call the base destructor and not be dispatched to the correct method. In most compilers (with vtable support) it can imply a change in the memory layout of the vtable for the type, and that means that the wrong method can be called and cause havoc.

  • add an argument with default value

This is a change in function signature, all code that used the method before will need to be recompiled to adapt to the new signature.


As soon as you change anything in the header file (hpp file), you have to recompile everything that depends on it.

However if you change the source file (cpp file), you have to recompile just the library which contains needs definitions from this file.

The easy way to break physical dependencies, where all libraries in the upper tier needs to recompile is to use the pimpl idiom. Then, as long as you don't touch the header files, you just need to compile the library where the implementation is being modified.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜