创建具有多个类的库的推荐方法是什么?

时间:2012-01-16 16:55:17

标签: c++ qt

我相对较新的C ++(之前的Python经验和涉猎Java)我正在编写一个小程序作为熟悉项目。作为程序的一部分,我正在编写一个类来解码一些数据,并最终编写一个类似的将执行编码的类。代码是我确信我会经常重用的东西,并认为创建一个库作为项目的一部分会很有趣。 我的问题是,什么被认为是创建图书馆的最佳做法?

编辑:(已修订)

在问了这个问题后,我意识到我不知道我不知道的是什么。我做了一些更多的研究,这应该有助于使我的问题更具体:

  • 我在Qt Creator中开发。因此,与Qt相关的细节会有所帮助,但不是必需的。
  • 我在Qt(MyCodec)中创建了一个新的静态库项目,目前已经定义了一个名为MyDecoder的类。
  • 作为一个库,我的假设是,要添加MyEncoder,我只需创建另一个类/头文件。
  • 接下来发生的事情是我不确定的地方。我只是建立图书馆吗?我的理解是它将创建(在Windows中).lib和.h文件。在这一步之前我应该​​做些什么吗?是否有影响我与之互动方式的选项?
  • 我是否只在程序中包含该头文件以访问我写的两个类?
  • 我在Qt中为项目添加.lib文件时找到了很多答案,因此我不需要这些信息。

原始问题:(针对具体情况)

我最初的想法是创建包含MyEncoder和MyDecoder类的MyLib是最方便的。

  • 如果我这样做,我只是在标题中声明这两个类吗?

  • 我想从这个库中创建一个DLL,以实现可移植性和体验。我确信有很多关于创建和使用DLL的信息(这不是这个问题的主题),但是如果有一个特别好的教程(对于Qt)请传递它。

  • 我的假设是,对于此实现而言,最好为MyEncoder和MyDecoder使用单独的命名空间,而不是MyLib的一个命名空间?

我可以看到这种方法的一个折衷是应用程序的大小,因为包括MyLib.h将包括编码器和解码器的代码(如果编码器和解码器是单独的应用程序)。这假设我没有使用DLL。

我想我得到的是:

  • 有哪些方法(推荐)?
  • 每个人的权衡是什么?
  • 我在哪里可以找到有关此特定主题的文档(教程/示例)?我的搜索努力没有取得多大成果。

如果它更具体,我在Qt Creator中使用Qt 4.7.4进行开发。

3 个答案:

答案 0 :(得分:6)

关于库,C ++中的一个“最佳实践”通常是“你付出的代价”。

这适用于您的问题是您将MyEncoder和MyDecoder放在单独的头文件中。因此,如果用户想要使用MyEncoder,他将包含MyEncoder.h,如果他想使用MyDecoder,他将包含MyDecoder.h,如果他想使用两者,他将包括两个头。

链接器通常只包含您在可执行文件中使用的代码部分,因此,就代码大小而言,没有任何代价,但编译时间会受到惩罚,特别是如果您开始使用高级模板技术在你的课堂上。在大型项目中编译时间可能会很长,因此只能包含您要使用的内容非常重要。

当然,有时候用一个标题包含所有东西也很方便。所以你可以拥有的是:

  • MyEncoder.h
  • MyDecoder.h
  • MyCodec.h

然后MyCodec.h可以包含MyEncoder.h和MyDecoder.h

将MyEncoder和MyDecoder放在不同的命名空间中可能没有充分的理由,假设它们是针对相同类型的数据进行操作的。

您可能希望拥有类似MyCodec名称空间的内容,并在该名称空间中声明MyEncoder和MyDecoder。

针对您的修订进行了更新:

  

作为一个库,我的假设是,添加MyEncoder,我只是创建   另一个类/头文件。

这是一个正确的假设。

  

接下来发生的事情是我不确定的地方。我只是建立了   图书馆?我的理解是它将创建一个.lib(在Windows中)   和.h文件。在这一步之前我应该​​做些什么吗?是   有哪些选项会影响我与它互动的方式?

我暂时没有使用过Qt创建者,所以我不能对它有权威或者如何访问相关选项。但作为一般规则,您将需要至少有两个版本的库;调试版本和发布版本。如果您的库使用Qt库,那么当应用程序链接到库的调试版本时,他们需要在其路径中具有Qt共享库的调试版本,如果它们链接到您的发行版本,则需要拥有Qt库的发布版本。

您可能还有一些选项可以静态链接到C ++标准运行时库,还是动态链接到DLL。

但基本上是的,您只需构建库,然后使用它的应用程序将库链接到可执行文件。

  

我是否只在程序中包含该头文件以访问它们   我写的课程?

您包含头文件,并链接到.lib文件。这就是你应该做的所有事情。

答案 1 :(得分:4)

这适用于Qt Creator 2.4.1。

首先,要使用和调试您的库,您将希望拥有一个包含两个子项目的分层顶级项目:一个用于库,另一个用于使用它的应用程序。这样,Creator将重建在运行应用程序之前更改的所有内容 - 因此,如果您更改库源文件,库将自动重建以及应用程序将重新链接。

我将逐步展示如何创建所需的三个项目(库,应用程序和顶级项目),最后是一个可构建,可调试,最小的应用程序和库。您可以按照相同的步骤轻松添加更多子库。

创建项目

  1. 为您的项目创建一个文件夹。

  2. 热门项目

    • File->New File or Project, Other Project, Subdirs Project, Choose
    • 将项目命名为top,并将其放在您在步骤1中创建的文件夹中。
    • 点击"完成&添加子项目"
  3. 图书馆计划 - 在上一步中点击后会显示新项目窗口。

    • Other Project, C++ Library, Choose
    • 键入:更改为静态链接库。
    • 将项目命名为library,并将其放在先前创建的top文件夹中 步。默认情况下,您应该已经选择了此文件夹。
    • 点击,选择要由库项目使用的QtCore模块,将其他所有内容保留为默认值。
  4. 应用程序项目 - 右键单击​​“项目”窗格中的top项目(不在top.pro上)

    • Qt Widget Project, Qt Gui Application, Choose
    • 将项目命名为app并将其放入在步骤2中创建的top文件夹中。默认情况下,您已经选择了此文件夹。
    • 点击,将所有内容保留为默认设置。
  5. 您现在应该有一个可构建的top项目,其中applibrary作为子项目。按C-B(Ctrl-B或Command-B,具体取决于您的平台)来构建它。构建应该没有错误。

    设置依赖关系

    与图书馆链接

    app子项目尚未使用我们的库。要使用我们的库app链接:

    1. 右键单击“项目”窗格中的app(app.pro上),选择Add Library...

    2. 选择Internal Library,继续

    3. library是唯一的选择,已经被选中。点击直到结束。
    4. app项目现在静态链接到library。更改app项目中的任何文件(空格更改将执行)以强制构建,然后按C-B进行构建。构建应该通过OK。

      在库中添加顶级依赖关系

      如果库已更改,将重建app子项目,但在开始构建应用程序之前,没有任何东西可以保证make系统首先构建库子项目。此类信息属于顶级top.pro项目文件。

      打开top.pro,然后添加一行CONFIG += ordered。该文件应如下所示:

      TEMPLATE = subdirs
      
      SUBDIRS += \
          library \
          app
      
      CONFIG += ordered
      

      确保SUBDIRS的订购正确:库在应用之前首先出现。如果订单错误,您可以将条目按顺序排列。反斜杠\是一个行继续符。 \之后必须没有空格!

      保存文件(C-S),右键单击top项目并选择Rebuild Project" top"。构建应该没有错误地完成。警告没问题。

      选择构建

      在Qt Creator的左下方面板中,有一个面板,其中包含构建按钮(锤子),运行按钮(三角形),调试按钮(带有错误的三角形),构建选择器(a上面有top项目名称的笔记本电脑,以及下面的elided build配置。单击构建选择器,并确保选择以Debug结尾的Build。此构建配置为生成所需的调试符号在调试器下运行它。我们稍后会需要它。

      实施图书馆

      我们现在将为Qt Creator为我们创建的Library类添加一些代码。打开library.hlibrary.cpp文件。添加的代码只是Library类中的静态方法。这个方法将在App中用来表明这两个部分实际上已被链接。这两个文件的内容如下。

      //library.h
      #ifndef LIBRARY_H
      #define LIBRARY_H
      
      #include <QString>
      
      class Library {
      public:
          Library();
          static QString string();
      };
      
      #endif // LIBRARY_H
      
      //library.cpp
      #include "library.h"
      
      Library::Library()
      {}
      
      QString Library::string()
      {
          return "I come from the library";
      }
      

      点击C-B,代码应该重建而不会出错。

      实施申请

      现在让我们使用app中的库API。打开mainwindow.cppmainwindow.h文件并修改如下。

      //mainwindow.h
      #ifndef MAINWINDOW_H
      #define MAINWINDOW_H
      
      #include <QScopedPointer>
      #include <QMainWindow>
      
      namespace Ui {
      class MainWindow;
      }
      
      class MainWindow : public QMainWindow
      {
          Q_OBJECT
      
      public:
          explicit MainWindow(QWidget *parent = 0);
          ~MainWindow();
      
      private:
          QScopedPointer<Ui::MainWindow> const ui;
      };
      
      #endif // MAINWINDOW_H
      
      //mainwindow.cpp
      #include <QVBoxLayout>
      #include <QLabel>
      #include "mainwindow.h"
      #include "ui_mainwindow.h"
      #include "library.h"
      
      MainWindow::MainWindow(QWidget *parent) :
          QMainWindow(parent),
          ui(new Ui::MainWindow)
      {
          ui->setupUi(this);
          QVBoxLayout * layout = new QVBoxLayout();
          QLabel * label = new QLabel(Library::string(), centralWidget());
          layout->addWidget(label);
          centralWidget()->setLayout(layout);
      }
      
      MainWindow::~MainWindow()
      {}
      

      点击C-B,项目应该建立好。

      然后我们可以在Library::string()实现中设置断点,以便不仅调用代码,而且调试器跨子项目工作。在library.cpp中,点击return "I come from the library";行中行号左侧的灰色区域。在您单击的位置会出现带有小沙漏的红色圆圈:这表示已设置挂起断点。

      接下来按C-Y在调试器中启动项目。唯一可运行的子项目是应用程序,它将自动被选为启动项目。

      最终断点圈会丢失沙漏,因为调试器会读取符号文件并找出断点的位置。此后不久,代码将在Library :: string()方法中停止 - 它从MainWindow构造函数中调用。点击C-Y继续执行app

      现在将出现主窗口,其中可以看到I come from the library文本。此文本设置在MainWindow的构造函数中添加的标签上。

答案 2 :(得分:2)

库和类之间没有链接。库的概念早在OO之前出现,并且c / c ++中的库格式中没有任何内容与类或名称空间有关。

如果您正在使用静态库(而不是DLL),那么链接器将仅提取程序所需的那些函数,因此库的大小无关紧要。使用动态库(dll)你必须运送整个dll,所以有理由将类似的lib打包成一个dll - 这就是为什么Qt有例如QtOpenGl.dll与QtGui.dll分开 - 如果你不使用opengl不需要包含库。头文件的大小与完成的程序无关 - 尽管非常大的头文件会导致编译速度变慢(除非你有预编译的头文件)。

命名空间用于表示这些类一起工作并避免与其他类冲突,因此我将使用单个命名空间来编码/解码类。