C ++编译设计:安全地扩展一个类

时间:2013-05-18 10:58:59

标签: c++ compilation header-files

我有一个关于扩展已使用的标头,源和对象的问题。 在理解我的意思之前,你必须接受我想要使用这个设计:

在我的一个项目中,我只在标题中使用函数声明,而对于每个定义,我使用一个单独的源文件,它将编译为一个单独的目标文件。

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

标题可能如下所示:

文件: src / List.hpp

//[guard]
//[includes]

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

现在这三个函数将有单独的文件:

文件: src / List / add.cpp

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

想象另外2。

这些将在某个时刻进行编译,其他编译类中的

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

现在我们要调整List的标题,我们不想更改函数,我们只想添加一个函数:

文件: src / List.hpp

//[guard]
//[includes]

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

因此正在生成另一个源文件和目标文件,在此示例中调用它:src / List / find.cpp和src / List / find.o

现在我的问题是,这是使用标题,来源和对象的合法方式吗? 这会产生问题,还是根本不可能?

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

3 个答案:

答案 0 :(得分:1)

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

我的感觉是

  • 实际上(出于效率原因)有很多(通常很小的)inline函数,特别是内联成员函数(如getter,setter等......),通常包含在他们的class { .... }定义。

  • 因此,某些成员函数应该是inline,或者在类中

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

然后所有构造函数Foo::Foo(int),析构函数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)的较小文件都会从间接包含的文件中提取大量源代码。 < / p>

另请参阅this answer,为什么Google(与D.Novillo一起)努力向GCC添加preparsed headers,为什么LLVM / Clang(与C.​​Latner)在C中需要modules C ++。为什么Ocaml,Rust,Go,......有模块......

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

对于机器生成的C ++代码,我建议更多生成更少的文件,但要使它们更大。生成数十个几十行的小C ++文件是低效的(使总构建时间太长):编译器将花费大量时间一次又一次地解析相同的#include - d系统头,并实例化相同的模板类型(例如,当使用标准容器时)很多次。

请记住,C ++允许每个源文件有几个类(与Java相反(内部类除外))。

此外,如果生成了所有C ++代码,则实际上不需要生成头文件(或者您可能生成一个大的*.hh),因为您的生成器应该知道哪些类和&amp;函数实际上在每个生成的*.cc中使用,并且可以在该文件中仅生成那些有用的声明和内联函数定义。

P.S。:请注意inline(如register)只是编译器的一个(有用的)提示。它可以避免内联标记为inline的函数(甚至隐含地,在class定义内)。它还可以内联一些未标记为inline的函数。但是,编译器需要知道函数体以内联它。

答案 1 :(得分: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; };

答案 2 :(得分:0)

是的,这很好用。这就是静态库的实现方式,因为这样可以使链接器更容易引入未使用的东西。

相关问题