1

^ Not a duplicate, here I'm investigating why one method is preferred, not wich are the differencies, a carefull read would make it glaring to peer reviewers it is not a duplicate.

I was reading that:

GCC Visibility

And suddendly the question popped out in my mind. Is there any sane reason that make a static or shared library preferred over a plain Obj file?

When creating a static/shared library we lose much information and also chance for optimization (wich don't matters to me). Instead we could compile our source files into Obj files, and then link all Obj files (also the Obj files of the library) into the final executable.

=> we retain all info, especially usefull for exception handling and preventing duplicated type_info (dependency injection with C++ relies on C++11 Type_info, but there's no guarantee you'll not get a duplicated std::type_info object for different classes in different libraries).

If visibility warry you, you can do a "Ghost Compile/Link" step (compile application and then link with the static library to see if we get "undefined symbol" because accessing internal stuff) and then proceed with the real Compile/Link (everything to Obj files and then Obj to the Executable)

The final executable will be smaller and faster, there will not be issues with exceptions and the build system will be more Emscripten friendly :).

Do you see possible issues (I'm using already that and works flawless, but maybe already existing code may have "duplicate" issues? Maybe unfeasible for large codebases?)

SMALL EXAMPLE:

My static library is compiled from 2 files:

MyFoo.hpp

//declare MyFoo to be internal to my library
void __attribute__ ((visibility ("hidden"))) MyFoo();

MyFoo.cpp

#include <MyFoo.hpp>
#include <iostream>
void MyFoo(){
    std::cout<<"MyFoo()"<<std::endl;
}

MyBar.cpp

#include <MyBar.hpp>
#include <MyFoo.hpp>
#include <iostream>
void MyBar(){
    std::cout<<"MyBar() calling "; MyFoo(); //calling MyFoo
}

When we compile, we get 2 ObjFiles

MyFoo.o
MyBar.o

When we link we get

MyLib.a

MyBar, can still "see" and call MyFoo (otherwise it could not compile).

When I create my executable if I link against MyLib.a I can call only MyBar wich is correct

#include <MyBar.hpp>
#include <MyFoo.hpp>
int main(){
    MyBar(); //ok
    //MyFoo(); // error undefined symbol
    return 0;
}

That's because I lost some info (desired in most cases: note I had to specify hidden), but hence the "visibility" feature turns into a problem because hiding stuff we could end up with different classes (inside different libraries) that have the same full qualified names:

That's an issue when throwing exceptions, or when trying to use std::type_info

So the only viable solution seems doing a 2 steps compilation, 1 is just to check we are not breaking visibility (and hence the API contract), the 2nd build is to avoid the issues in the aforementioned link (strange to debug exceptions behaviour or misterious crashes).

Linking MyFoo.o,MyBar.o,main.o togheter would be conceptually wrong because allows compilation of the following code

#include <MyBar.hpp>
#include <MyFoo.hpp>
int main(){
    MyBar(); //ok
    MyFoo(); // compile NOT OK!
    return 0;
}

but linking object files togheter is the only way to avoid exceptions issues.

CoffeDeveloper
  • 6,929
  • 2
  • 31
  • 58
  • 2
    What information is lost when making libraries? They're just a bunch of object files concatenated with `ar`. – user657267 Jan 19 '15 at 01:59
  • When you play with visibility of classes no. You can `#include ` and use foo when creating your library (so foo visible at Object files level), but when you create a executable and link against the static library you get undefined symbol for foo (you lost information about foo). So actually hiding information is usually good, if you hide it to final user is good, but can become an issue with exception handling and dynamic_cast. – CoffeDeveloper Jan 19 '15 at 02:03
  • @DarioOO I'm still not understanding, can you make a small example showing what you mean? – user657267 Jan 19 '15 at 02:08
  • A static library is essentially a ZIP file of object files (although using a different format, not actually ZIP). – user253751 Jan 19 '15 at 02:29
  • @DarioOO: To be clear, `#include` includes source files, not object files. – Keith Thompson Jan 19 '15 at 02:29
  • To be clear, you need the definition of Foo at least, the example show more precisely what I mean – CoffeDeveloper Jan 19 '15 at 02:33
  • `different classes (inside different libraries) that have the same full qualified names:` This is what namespaces are for, having the exact same name doesn't only affect exceptions or type info, it's undefined behavior because it violates the ODR. If it's hidden it doesn't really matter anyway, the call inside `MyBar` will already be linked to the hidden `MyFoo` function when the library is created, there's no risk of it being relinked. – user657267 Jan 19 '15 at 02:41
  • DLL throws B, B is "internal" (aka hidden) C catch the exception, but the header of B included in C is a different type because previous B was hided .=> undefined behaviour. So we need to be aware of the type of B.That's exactly the point, the Visibility feature, (inherited by windows dllexport /import) turns into undefined bheaviour for exceptions, unless we take particular care on what is going on under the hood.. One way to fix that is linking togheter all Obj files, but that ignores visibility, so first we need to check if the static lib build and link succed. – CoffeDeveloper Jan 19 '15 at 02:50
  • this is a (not so much ) extreme case. Because B could be hided without me have knowledge of it (even if I specified it to be "default" many things may override that and hide it, and that's hell to debug). Also I could still have 2 same identical full qualified names in different libraries without any undefined behaviour if both names are "hidden", the only culprit is "std::type_info" would be duplicated for both (so again we need to check every class we use with std::type_info is not "hided". That's all boring stuff that can be easily catched by a 2 step build – CoffeDeveloper Jan 19 '15 at 02:57

2 Answers2

2

Hidding Things in C++

You may want to learn a bit more about how, as a programmer in C++, you are expected to hide private definitions. One of the main option is the Pimpl Idiom:

Why should the "PIMPL" idiom be used?

This makes the Foo.hpp include totally private, or even part the definitions directly inside the Bar.cpp implementation (depends on how big it's going to be.)

# project organization
include/Bar.hpp
src/Bar.cpp
src/Foo.hpp
src/Foo.cpp

The files under include will be shared with other projects. Anything under src is 100% private (as an example, if you look at the Qt source code, you will see <name>Private.h files which are pimpl.)

In Bar.hpp you cannot reference Foo, also you can use its pointer if necessary:

class Foo;

class Bar
{ 
   ...
   std::shared_ptr<Foo> f_foo;
   ...
};

Then in the .cpp you include the .hpp to get the actual definition:

#include "Bar.hpp"
#include "Foo.hpp"
...
// Bar implementation
...

You could also implement Foo inside Bar.cpp assuming it is not too large. That way you can also use a no name namespace (i.e. hiding the declaration without g++ tricks):

// in Bar.cpp

namespace
{

Class Foo
{
    ...implementation of Foo...
};

} // no name namespace

Class Bar
{
    ...implementation of Bar, can reference Foo...
};

Circumvent the Loss of Information

Now, your main point is about the loss of information because now you have no clue what Foo is. One of your comments: I won't be able to dynamic_cast<Foo *>(ptr). Really? If Foo is expected to be private, then why should your users be able to dynamic cast to it?!? If you think you can give me any one reason for that, just know that you're just plain wrong.

Also Foo could throw a private exception:

class Foo
{
   ...
   void func() { throw FooException("ouch!"); }
   ...
};

You have two main solutions to that problem.

When you implement Foo you know it is, itself, going to be private, so the simplest way is to never raise a private exception (which does not make much sense in general, but you may want to have such...)

If you have to have private exceptions, you have to have a conversion of the exception. So in Bar, you catch all private exceptions from Foo and convert them to a public exception and then throw that. Frankly, that is a lot more work, for one thing, but on top of that you're going to throw twice: slow!

class Foo
{
   ...
   // Solution 1, use a public exception from the start
   void func() { throw BarException("ouch!"); }
   ...
};

// Solution 2, convert the exception
class Bar
{
    void func()
    {
        try
        {
           f_foo.func();
        }
        catch(FooException const& e)
        {
            throw BarException(e.what());
        }
    }
};

At times you do not have a choice but to catch exceptions and convert them because the sub-class cannot directly throw your public exceptions (maybe it's a 3rd party library.) But frankly, just use public exceptions if you can.

Expansion on the Subject

Since you look like you are interested by the fact that you get a large loss of information between libraries, you may be interested by the Ada language instead of C++. In Ada, the object files actually include all the information you need to do further compiling other packages and your executable, not just further linking. Since Ruby on Rail is a spawn of Ada and Eiffel, you certainly have the same effects appear in that language object files, although I'm not familiar with it so I cannot really tell (although I don't see how they would make it work without such!)

Otherwise, the C/C++ object files and libraries have been explained by others. I don't have much to add to their own comments. Note that those exist since the 60's (maybe early 70's) and although the format evolved quite a bit, the file still are very much limited to text (i.e. compiled + assembled code) blocks as in the old days.

Community
  • 1
  • 1
Alexis Wilke
  • 15,168
  • 8
  • 60
  • 116
  • If think you are missing the point, the example about visibility was on request of a commenter, I would never do such design decision (hiding API with "preprocessor", instead I already have src folder..) No sane person would raise a private exception, but if someone starts playing with visibility because need it (when you "need" to set visibility is explained in the link) it is likely it will override global visibility settings wich would make exceptions accidentaly private despite good intentions. The point was exactly of using Obj directly (and that would indirectly ignore visibility) – CoffeDeveloper Jan 19 '15 at 04:33
1

A library is convenient packaging.

  • A large body of code will usually have many object files. Managing linking with each individual object file can be unnecessarily confusing and overwhelming for the end user of the library.

  • Object files may change names between versions, even if the interface is the same, and if there is no difference to the end user. Keeping these changes inside the library abstraction makes life simpler for the end user.

  • Like a toolbox, it is easier to have related functionality in a common place.

levis501
  • 3,753
  • 20
  • 22
  • so I can just link everything to a Library.o wich is a object file (even better, compile directly to a single obj from many source files) but still not a library in the usual meaning and I have all the 3 good points you mentioned above. – CoffeDeveloper Jan 19 '15 at 02:59
  • 1
    While linking with static library linker can omit unneeded object files (ones that no one uses), which may result in smaller executable. – keltar Jan 19 '15 at 03:10