c ++ - 使用头文件/源文件分隔接口和实现

时间:2015-09-12 01:06:56

标签: c++

在C ++中,类通常声明如下:

// Object.h

class Object
{
    void doSomething();
}

// Object.cpp

#include "Object.h"

void Object::doSomething()
{
    // do something
}

据我所知,这可以缩短编译时间,因为只要您更改实现或接口,将类放在一个文件中就可以重新编译它(参见this)。

然而,从OOP和OOP的角度来看,我不知道界面与实现的分离有何帮助。我已经阅读了很多其他问题和答案,但我遇到的问题是,如果你正确地定义了一个类的方法(在单独的头文件/源文件中),那么你怎么能做出不同的实现呢?如果在两个不同的地方定义Object :: method,那么编译器将如何知道要调用哪一个?您是否在不同的名称空间中声明Object :: method定义?

任何帮助都将不胜感激。

3 个答案:

答案 0 :(得分:3)

如果您想在同一个程序中使用一个接口和多个实现,那么您可以使用抽象虚拟基础。

像这样:

class Printer {
    public:
    virtual void print_string(const char *s) = 0;
    virtual ~Printer();
};

然后你可以实现:

class EpsonPrinter : public Printer {
    public:
    void print_string(const char *s) override;
};

class LexmarkPrinter : public Printer {
     public:
     void print_string(const char *s) override;
};

另一方面,如果您正在查看实现操作系统独立性的代码,它可能有多个子目录,每个操作系统一个。头文件是相同的,但Windows的源文件仅为Windows构建,Linux / POSIX的源文件仅为Linux构建。

答案 1 :(得分:1)

  

如果你在两个不同的地方定义Object :: method,那么编译器将如何知道要调用哪一个?

它不会,实际上你将打破一个定义规则"如果你这样做,导致未定义的行为,根据标准不需要诊断。

如果要为类接口定义多个实现,则应以某种方式使用继承。

您可以这样做的一种方法是,使用虚拟基类并覆盖不同子类中的一些方法。

如果要将类的实例作为值类型进行操作,则可以使用pImpl惯用法和虚拟继承。所以你会有一个类,"指针" class,公开接口,并保存指向抽象虚拟基类类型的指针。然后,在.cpp文件中,您将定义虚拟基类,并定义它的多个子类,并且pImpl类的不同构造函数将实例化不同的子类作为实现。

如果你想使用 static 多态,而不是运行时多态,你可以使用CRTP习惯用法(它最终仍然基于继承,而不是虚拟继承)。

答案 2 :(得分:1)

  

但是,从[OOP]的角度来看,我不知道界面与实现的分离有多大帮助。

从OOP的角度来看,它没有帮助 ,并且不打算这样做。这是C ++的文本包含功能,它继承自C,这种语言不直接支持面向对象的编程。

模块化的文本包含是从汇编语言中借用的一个特性。它几乎是面向对象编程的对立面,或者基本上是计算机程序组织领域的任何好东西。

文本包含允许您的C ++编译器与古代目标文件格式互操作,这些格式不存储有关符号的任何类型信息。 Object.cpp文件已编译为此对象格式,从而生成Object.o文件或Object.obj或您平台上的内容。当程序的其他部分使用此模块时,它们几乎完全信任Object.h中写入的信息。除了带有数字信息(如偏移和大小)的符号外,Object.o文件中没有任何有用的内容。如果标题中的信息没有正确反映Object.obj,则表示存在未定义的行为(在某些情况下,由于C ++对函数重载的支持而减轻,这会将不匹配的函数调用转换为由于名称错误而无法解决的符号。

例如,如果标头声明了变量extern int foo;但是目标文件是编译double foo = 0.0;的结果,则意味着程序的其余部分正在以{double对象的形式访问int对象{1}}。阻止这种情况发生的是Object.cpp包含它自己的头(从而迫使编译器捕获声明和定义之间的不匹配),并且你有一个健全的构建系统来确保{{1}如果有任何事情涉及Object.cpp,则重建。如果该检查是基于时间戳的,那么您还必须拥有一个理智的文件系统和版本控制系统,它不会使用时间戳做一些古怪的事情。