在C ++中,类通常声明如下:
// Object.h
class Object
{
void doSomething();
}
// Object.cpp
#include "Object.h"
void Object::doSomething()
{
// do something
}
据我所知,这可以缩短编译时间,因为只要您更改实现或接口,将类放在一个文件中就可以重新编译它(参见this)。
然而,从OOP和OOP的角度来看,我不知道界面与实现的分离有何帮助。我已经阅读了很多其他问题和答案,但我遇到的问题是,如果你正确地定义了一个类的方法(在单独的头文件/源文件中),那么你怎么能做出不同的实现呢?如果在两个不同的地方定义Object :: method,那么编译器将如何知道要调用哪一个?您是否在不同的名称空间中声明Object :: method定义?
任何帮助都将不胜感激。
答案 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
,则重建。如果该检查是基于时间戳的,那么您还必须拥有一个理智的文件系统和版本控制系统,它不会使用时间戳做一些古怪的事情。