2013-05-18 32 views
1

我有一个关于扩展已经使用的头,源和对象的问题。 在了解了我的意思,你不得不接受,我想用这样的设计:C++编译设计:安全地扩展一个类

在我的项目,我的头只使用函数声明,并为每个定义我使用一个单独的源文件,它将编译为一个单独的对象文件。

假设我在目录“src”中有一个名为List的非常简单的类。

头看起来是这样:

文件:的src/List.hpp

//[guard] 
//[includes] 

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

现在三大功能将有单独的文件:

文件:的src /目录/add.cpp

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

想象其他2.

这些将被在某一时刻编译,头文件其他编译的类使用

我们假设另一个名为ABC的类使用List的头文件。 对于ABC类中的每个函数,都会生成一个对象文件。

现在我们要调整列表的标题,我们不想改变的功能,我们只需要添加一个功能:

文件:的src/List.hpp

//[guard] 
//[includes] 

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

所以另一个源文件和目标文件被产生,这就是所谓的在这个例子中:SRC /列表/ find.cpp和src /列表/ find.o

现在我的问题,这是用头一个合法的方式,来源和对象? 这会产生问题,还是完全不可能?

此外,在类ABC中名为List的类仍与新创建的名为List的类相同吗?

+0

你的'class List'应该有成员数据字段(或者应该继承一些)。 –

+0

@BasileStarynkevitch无关紧要,只是增加了一个不改变任何成员的函数 – Tim

+0

你可能有这样的头文件,但是在配置你的构建器时可能会遇到麻烦(例如你的'Makefile' ) –

回答

1

您的设计看起来可行。但是,我不会推荐它。你没有提到模板或标准容器。

我的感觉是

  • 有很多的(通常很小)inline功能,实际上是重要的(出于效率的考虑),特别是内联成员函数(如干将,制定者,等等...... ),通常包含在它们的class类别{ .... }的定义。

  • 因此,一些成员函数应该是inline,无论是类,那么所有的构造Foo::Foo(int)的内部等

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

,析构函数Foo::~Foo和成员函数int Foo::f(int)内联

或班后(通常更容易生成机器码)如

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; }; 

在这两种情况下,您都需要内联(或者可能是链接时间优化gcc -flto -O用于编译&链接)。

编译器只有在知道它们的定义(它们的主体)时才能内联函数。

  • 然后,你每次都在#include有些类的定义。你需要以某种方式获得编译的内联函数定义。要么你把它放在相同的标题,或标题本身应#include一些其他的文件特别是(提供的内联函数的定义)

  • 在一般情况下,使用标准C++库时(并使用标准的容器,所以如#include <vector>)你会得到很多系统头文件(间接包含)。实际上,你不需要非常小的实现文件(即每个文件有几十行源代码是不切实际的)。同样,现有的C++框架库也会提取很多(间接)标题(例如#include <QtGui>会带来很多代码)。

  • 我建议让C++源文件(*.hh*.cc)至少有一千行。

查看预处理代码的大小,例如,与g++ -H -C -E ...你会在实践中感到害怕:即使在编译几十行源代码的小型C++文件时,也会有数千个预处理的源代码行。

因此,我建议千行源文件:使用C++标准库或某些C++框架库(Boost,Qt)的任何较小文件都会从间接包含的文件中提取大量源代码行。

又见this answer,为什么谷歌(与D.Novillo)力图增加preparsed headers到GCC,为什么LLVM/Clang的(与C.Latner)希望在C和C++ modules。为什么Ocaml,Rust,Go,...有模块...

你也可以看看GCC生成的GIMPLE表示,使用MELT probe(MELT是扩展GCC的领域特定语言,探针是一个简单的图形界面来检查像Gimple这样的GCC的一些内部表示),或者使用GCC的-fdump-tree-all选项(注意:该选项会生成数百个转储文件)。您也可以将-ftime-report传递给GCC,以便更多地了解它在编译C++代码时所花费的时间。

对于机器生成的C++代码,我建议更产生更少的文件,但使他们大。生成数千个每行几十行的小型C++文件效率不高(总编译时间过长):编译器将花费大量时间反复解析相同的系统头文件,并实例化相同的模板类型(例如,当使用标准容器时)很多次。

记住,C++允许有每个源文件的几个类(反之到Java(除内部类))。另外,如果生成了所有的C++代码,您并不需要生成头文件(或者您可能会生成一个单独的大文件),因为您的生成器应该知道每个生成的函数实际上使用了哪些类*.cc,并且可以在该文件中仅生成那些有用的声明和内联函数定义。

P.S .:注意inline(如register)只是编译器的一个(有用的)暗示。它可能会避免内联一个标记为inline的函数(甚至可以隐式地在class定义中)。它也可以内联一些没有标记为inline的功能。但是,编译器需要知道函数的主体来将其内联。

+0

感谢您的回答。机器生成,但遵循一些规则,但它不构建模板函数,但使用std容器,但我不知道函数d源文件中的定义没有被内联。虽然似乎合乎逻辑。 LLVL/Clang的模块文档看起来很有希望。感谢您的帮助! – Tim

+0

我想,但我仍然想要其他建议/反馈/意见。 – Tim

+0

Eh,源文件内部的函数定义通常是内联的。然而,为了内联一个函数,编译器需要知道它的定义(即它的主体)...... –

1

我相信函数是否内联由编译器决定(请参阅问题How will i know whether inline function is actually replaced at the place where it is called or not?)。为了内联一个函数(尽管这并不一定意味着该函数在编译过程中会被内联),您应该在其类中定义该函数,或者在标头中定义函数之外使用“inline”命令。例如:

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

我认为'inline'关键字应该放在'class {'...'}'里面;定义。 –

+0

这意味着链接器可以内联一个在目标文件中定义的函数吗? – Tim

+1

只要记住,内联关键字是一个提示,而不是指令。含义:即使你写这些也不能内联。 – MatiasFG

0

是的,这工作得很好。这就是静态库的实现方式,因为这样可以让连接器更容易避免使用未使用的东西。