8

我正在编译一个 C++ 静态库,并且由于所有类都是模板化的,因此类定义和实现都在头文件中。结果,似乎(在 Visual Studio 2005 下)我需要创建一个包含所有其他头文件的 .cpp 文件,以便它正确编译到库中。

为什么是这样?

4

9 回答 9

8

编译器不编译头文件,因为这些头文件旨在包含在源文件中。在进行任何编译之前,预处理器会从任何包含的头文件中获取所有代码,并将该代码放入包含它们的源文件中,就在它们被包含的位置。如果编译器也应该编译头文件,例如,你会在很多事情上有多个定义。

例如,这是预处理器看到的:

[foo.h]
void foo();

--

[mysource.cpp]
#include "foo.h"

int main()
{
   foo();
}

这就是编译器看到的:

[mysource.cpp]
void foo();

int main()
{
   foo();
}
于 2009-03-08T14:20:04.880 回答
4

即使您将创建一个 .cpp 文件,您仍然不会收到任何东西。您需要实例化模板才能将它们放入库中。

看看这里 http://www.parashift.com/c%2B%2B-faq-lite/templates.html#faq-35.13 关于如何用具体类型实例化模板。

于 2009-03-08T14:17:14.107 回答
2

在 C++ 中,模板只是实际类的元定义。当您编译模板类时,编译器实际上会为传入的特定类型的数据动态生成实际类的代码(模板只是要复制的“模式”)。

例如,如果您有以下代码


struct MyTemplate
{
private:
    float MyValue;

public:
    float Get() { return MyValue; }
    void Set(float value) { MyValue = value; }
};

void main()
{
    MyTemplate v1;
    MyTemplate v2;
    v1.Set(5.0f);
    v2.Set(2);
    v2.Get();
}

编译器实际看到的是


struct CompilerGeneratedNameFor_MyTemplate_float
{
private:
    float MyValue;

public:
    float Get() { return MyValue; }
    void Set(float value) { MyValue = value; }
};

struct CompilerGeneratedNameFor_MyTemplate_int
{
private:
    int MyValue;

public:
    int Get() { return MyValue; }
    void Set(int value) { MyValue = value; }
};

void main()
{
    CompilerGeneratedNameFor_MyTemplate_float v1;
    CompilerGeneratedNameFor_MyTemplate_int v2;
    v1.Set(5.0f);
    v2.Set(2);
    v2.Get();
}

正如您可能看到的,编译器实际上并不知道要生成什么代码,直到您真正声明了模板的实例。这意味着模板不能编译到库中,因为它不知道模板最终会是什么。好消息是,如果您只是分发包含模板定义的头文件,您实际上不需要编译或包含任何库。

此外,作为旁注,“#include”预编译器命令实际上只是告诉预编译器用该文件中的所有内容替换“#include”。

于 2009-03-08T14:45:37.720 回答
2

如果您的所有代码都在 .h 文件中,那么您无需编译静态库即可使用该代码。

所有代码在编译时都可供库使用,因此在链接时不需要任何东西。

于 2009-03-08T17:49:05.627 回答
1

你试图创造一些不必要的东西。大多数 C 库(和所有 C++ 库)都分为两部分分发:

  • 接口 ( foo.h)
  • 实施 ( foo.lib)

对于 C++ 模板代码,您的所有库都必须由最终用户编译,因为这就是模板的工作方式。没有理由提供预编译库。在这种情况下,您可以将您的库分发视为:

  • 接口 ( foo.h)
  • 实施 ( foo-inl.h)

正如 Niel 上面所说的,仅出于您自己的测试目的而拥有实现很有用,并且可能值得将这些实现与库本身一起分发。所以你应该有一套单独的单元测试来练习你的代码;但这些测试不应该是库本身的一部分。

于 2009-03-08T16:43:00.980 回答
1

如果您的库全部在头文件中实现,则无需构建任何二进制文件即可使用它。那就是说。我通常在头文件库的初始开发阶段创建一个 .cpp 文件。为什么?在实际使用模板之前,编译器不会尝试编译甚至解析模板。拥有一个 .cpp 文件并在那里有一些虚拟代码来实例化模板可以帮助我在开发过程中更早地发现语法错误。所以我可以添加一些模板代码,点击编译,修复语法错误,添加更多代码,编译......等等。如果您在添加数百行代码后都尝试寻找一些愚蠢的语法错误,您就会明白我的意思。一旦我的库准备好进行单元测试,我将删除 .cpp 文件并依靠单元测试来推动我的开发。

此外,如果您只使用 VC++ 编译代码,您需要注意的一件事是 VC++ 在实际使用之前不会尝试编译所有模板成员函数。例如:

template <typename T>
class MyTemplate
{
public:
    MyTemplate() {} // default constructor

    MyTemplate(int) { 
        1 = 2
        // other syntax error code here
    }
};

void f() { MyTemplate<int> myt(); } // compile fine in VC
void g() { MyTemplate<int> myt(1); } // syntax error 

f() 将在 VC++ 2003 中正常编译,g++ 将捕获语法错误。我认为VC8和VC9也有同样的问题。

于 2009-05-01T15:00:09.810 回答
0

想想标准模板库。当您在另一个项目中使用它们时,您的模板类将被编译。

于 2009-03-08T14:21:17.340 回答
0

其他人所说的关于未编译到库中的模板是正确的。但是,仍然值得强制编译器看到它们(通过#将它们包含在 .cpp 文件中),因为这样至少会检查它们的语法。

于 2009-03-08T14:23:41.157 回答
0

如果你所有的类都是模板,你不需要生成 .lib,看看他们没有分发的 .lib 的 boost 或 stlport [1]。

模板在使用时被编译。

[1] 严格来说,他们为正则表达式、iostream 等更高级的功能分发库,但辅助库被其他模板使用,模板本身不以库的形式分发。

于 2009-03-08T17:37:52.190 回答