Tim
Tim

Reputation: 5681

C++ Compilation Design: Safely extending a class

I have a question about extending already used headers, sources and objects. Before understanding what I mean you just have to accept that I want to use this design:

In a project of mine, I use only function declarations in a header, and for each definition I use a seperate source file, which will compile to a seperate object file.

Let's say I have a very simple class called List in directory "src".

The header could look like:

File: src/List.hpp

//[guard]
//[includes]

class List {
    void add(int value);
    void remove(int index);
    void clear();
};

Now the three functions would have seperate files:

File: src/List/add.cpp

void List::add(int value) {
    // Do something
}

imagine the other 2.

These will be compiled at a certain moment, and the header file will be used in other compiled classes.

Let's assume that another class called ABC uses the header file of List. For each function in the class ABC, a object file is generated.

Now we want to adjust the header of List, we don't want to change a function, we only want to add a function:

File: src/List.hpp

//[guard]
//[includes]

class List {
    void add(int value);
    int find(int value);
    void remove(int index);
    void clear();
};

So another source file and object file is being generate, it's called in this example: src/List/find.cpp and src/List/find.o

Now my question, is this a legal way to use headers, sources and objects? Would this generate problems, or isn't this possible at all?

Also, is the class called List in class ABC still the same as the newly created class called List?

Upvotes: 1

Views: 198

Answers (3)

Your design seems workable. However, I won't recommend it. And you didn't mention templates or standard containers.

My feeling is that

  • it is practically important (for efficiency reasons) to have a lot of (usually small) inline functions, notably inlined member functions (like getters, setters, etc...), often contained in their class Class { .... } definition.

  • hence, some of the member functions should be inline, either inside the class like

    class Foo { 
      int _x;
      Foo(int x) : _x(x) {};
      ~Foo() { _x=0; };
      int f(int d) const { return _x + d; };
    }
    

then all of constructor Foo::Foo(int), destructor Foo::~Foo and member function int Foo::f(int) are inlined

or after the class (usually easier for machine generated code) like

    class Foo {
       int _x;
       inline Foo(int x);
       inline ~Foo();
       inline int f(int d) const;
    };

    Foo::Foo(int x) { _x = x; };
    Foo::~Foo() { _x = 0; };
    int Foo::f(int d) const { return _x+d; };

In both cases you need inlining (or perhaps Link Time Optimization e.g. gcc -flto -O for compiling & linking) for efficiency reasons.

The compiler can only inline functions when it knows their definition (their body).

  • then, every time you #include some class definition. you need to somehow get that inline function definition compiled. Either you put it in the same header, or that header should itself #include some other file (providing the definition of inlined functions)

  • In general, especially when using standard C++ library (and using standard containers, so e.g. #include <vector>) you'll get a lot of system headers (indirectly included). In practice, you don't want very small implementation file (i.e. having a few dozen lines of your source code per file is impractical).

  • Likewise, existing C++ framework libraries pull a lot of (indirect) headers (e.g. #include <QtGui> brings a big lot of code).

  • I suggest having C++ source files (either *.hh or *.cc) of a thousand lines each at least.

Look at the size of the preprocessed code, e.g. with g++ -H -C -E ... you'll be scared in practice: even when compiling a small C++ file of a few dozen lines of your source code, you'll have thousands of preprocessed source lines.

Hence my advice of thousand lines source file: any smaller file using the C++ standard library or some C++ framework library (Boost, Qt) is pulling a lot of source lines from indirectly included files.

See also this answer, why Google (with D.Novillo) tries hard to add preparsed headers to GCC, why LLVM/Clang (with C.Latner) wants modules in C and C++. And why Ocaml, Rust, Go, ... have modules...

You could also look at the GIMPLE representation generated by GCC, either using the MELT probe (MELT is a domain specific language to extend GCC, the probe is a simple graphical interface to inspect some internal representations of GCC like Gimple), or using -fdump-tree-all option to GCC (be careful: that option produces hundreds of dump files). You may also pass -ftime-report to GCC to understand a bit more where it is passing its time when compiling your C++ code.

For machine generated C++ code I recommend even more generating less files, but make them bigger. Generating thousands of small C++ files of a few dozen lines each is inefficient (making the total build time too long): the compiler will spend a lot of time parsing the same #include-d system headers again and again, and instantiating the same templated types (e.g. when using standard containers) a lot of times.

Remember that C++ permits to have several classes per source file (contrarily to Java (except inner classes)).

Also, if all of your C++ code is generated, you don't really need to generate header files (or you might generate one single big *.hh) because your generator should know which classes & functions are really used in each generated *.cc and could generate in that file only those useful declarations and inlined functions definitions.

P.S.: Notice that inline (like register) is just a (useful) hint to the compiler. It may avoid inlining a function marked inline (even implicitly, when inside class definition). It may also inline some functions not marked inline. However, a compiler needs to know the body of a function to inline it.

Upvotes: 1

Pete Becker
Pete Becker

Reputation: 76335

Yes, that works just fine. It's how static libraries are implemented, because that makes it easier for the linker to not pull in things that aren't used.

Upvotes: 0

Matzar
Matzar

Reputation: 243

I believe whether a function is inlined is determined by the compilator (see question How will i know whether inline function is actually replaced at the place where it is called or not?). To inline a function (though this does not necessarily mean the function will absolutely be inlined at compilation) you should either define the function inside its class, or use the command "inline" before defining your function outside of it, in the header. For example :

inline int Foo::f(int d) const { return _x+d; };

Upvotes: 1

Related Questions