使用头文件/源文件来分离接口和实现

时间:2021-12-07 15:06:49

In C++, classes are usually declared like this:

在c++中,类通常是这样声明的:

// Object.h

class Object
{
    void doSomething();
}

// Object.cpp

#include "Object.h"

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

I understand that this improves compile time because having the class in one file makes you recompile it whenever you change either the implementation or the interface (see this).

我理解这提高了编译时间,因为在一个文件中有类可以使您在更改实现或接口时重新编译它(见此)。

However, from and OOP point of view, I don't see how separating the interface from the implementation helps. I've read a lot of other questions and answers, but the problem I have is that if you define the methods for a class properly (in separate header/source files), then how can you make a different implementation? If you define Object::method in two different places, then how will the compiler know which one to call? Do you declare the Object::method definitions in different namespaces?

但是,从OOP的角度来看,我不认为将接口与实现分离有什么帮助。我已经阅读了很多其他的问题和答案,但我的问题是,如果您正确地定义了类的方法(在单独的头/源文件中),那么如何实现不同的实现呢?如果在两个不同的位置定义Object::方法,那么编译器如何知道调用哪个?您是否在不同的名称空间中声明对象::方法定义?

Any help would be appreciated.

如有任何帮助,我们将不胜感激。

3 个解决方案

#1


1  

However, from [an] OOP point of view, I don't see how separating the interface from the implementation helps.

然而,从OOP的角度来看,我不认为将接口与实现分离有什么帮助。

It doesn't help from an OOP point of view, and isn't intended to. This is a text inclusion feature of C++ which is inherited from C, a language that has no direct support for object-oriented programming.

从OOP的角度来看,这并没有帮助,也不是故意的。这是c++的一个文本包含特性,它继承自C语言,这是一种对面向对象编程没有直接支持的语言。

Text inclusion for modularity is a feature borrowed, in turn, from assembly languages. It is almost an antithesis to object-oriented programming or basically anything that is good in the area of computer program organization.

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

Text inclusion allows your C++ compiler to interoperate with ancient object file formats which do not store any type information about symbols. The Object.cpp file is compiled to this object format, resulting in an Object.o file or Object.obj or what have you on your platform. When other parts of the program use this module, they almost solely trust the information that is written about it in Object.h. Nothing useful emanates out of the Object.o file except for symbols accompanied by numeric information like their offsets and sizes. If the information in the header doesn't correctly reflect Object.obj, you have undefined behavior (mitigated, in some cases, by C++'s support for function overloading, which turns mismatched function calls into unresolving symbols, thanks to name mangling).

文本包含允许c++编译器与不存储任何符号类型信息的古老对象文件格式进行互操作。对象。cpp文件被编译为这种对象格式,从而产生一个对象。o文件或对象。obj或者你的平台上有什么。当程序的其他部分使用这个模块时,它们几乎完全信任Object.h中关于它的信息。没有什么有用的东西从物体中散发出来。o文件,除了符号以外的数字信息,如它们的偏移量和大小。如果标题中的信息不能正确地反映对象。obj,您有未定义的行为(在某些情况下,由于c++对函数重载的支持,可以减轻这种行为,由于名称管理,将不匹配的函数调用转换为不解析符号)。

For instance if the header declares a variable extern int foo; but the object file is the result of compiling double foo = 0.0; it means that the rest of the program is accessing a double object as an int. What prevents this from happening is that Object.cpp includes its own header (thereby forcing the mismatch between the declaration and definition to be caught by the compiler) and that you have a sane build system in place which ensures that Object.cpp is rebuilt if anything touches Object.h. If that check is based on timestamps, you must also have a sane file system and version control system that don't do wacky things with timestamps.

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

#2


3  

If you want one interface and multiple implementations in the same program then you use an abstract virtual base.

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

Like so:

像这样:

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

Then you can have implementations:

然后可以实现:

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

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

On the other hand, if you are looking at code which implements OS independence, it might have several subdirectories, one for each OS. The header files are the same, but the source files for Windows are only built for Windows and the source files for Linux/POSIX are only built for Linux.

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

#3


1  

If you define Object::method in two different places, then how will the compiler know which one to call?

如果在两个不同的位置定义Object::方法,那么编译器如何知道调用哪个?

It won't, and in fact you will be breaking the "One Definition Rule" if you do this, which results in undefined behavior, no diagnostic required, according to the standards.

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

If you want to define multiple implementations for a class interface, you should use inheritance in some way.

如果您想为类接口定义多个实现,那么应该以某种方式使用继承。

One way that you might do it is, use a virtual base class and override some of the methods in different subclasses.

一种方法是使用虚拟基类并覆盖不同子类中的一些方法。

If you want to manipulate instances of the class as value types, then you can use the pImpl idiom, combined with virtual inheritance. So you would have one class, the "pointer" class, which exposes the interface, and holds a pointer to an abstract virtual base class type. Then, in the .cpp file, you would define the virtual base class, and define multiple subclasses of it, and different constructors of the pImpl class would instantiate different of the subclasses as the implementation.

如果希望将类的实例作为值类型进行操作,那么可以使用pImpl习语,并结合虚拟继承。因此,您将有一个类,“指针”类,它公开接口,并保存指向抽象虚拟基类类型的指针。然后,在.cpp文件中,您将定义虚拟基类,并定义它的多个子类,pImpl类的不同构造函数将实例化不同的子类作为实现。

If you want to use static polymorphism, rather than run-time polymorphism, you can use the CRTP idiom (which is still ultimately based on inheritance, just not virtual inheritance).

如果您希望使用静态多态性,而不是运行时多态性,您可以使用CRTP习惯用法(它仍然是基于继承,而不是虚拟继承)。

#1


1  

However, from [an] OOP point of view, I don't see how separating the interface from the implementation helps.

然而,从OOP的角度来看,我不认为将接口与实现分离有什么帮助。

It doesn't help from an OOP point of view, and isn't intended to. This is a text inclusion feature of C++ which is inherited from C, a language that has no direct support for object-oriented programming.

从OOP的角度来看,这并没有帮助,也不是故意的。这是c++的一个文本包含特性,它继承自C语言,这是一种对面向对象编程没有直接支持的语言。

Text inclusion for modularity is a feature borrowed, in turn, from assembly languages. It is almost an antithesis to object-oriented programming or basically anything that is good in the area of computer program organization.

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

Text inclusion allows your C++ compiler to interoperate with ancient object file formats which do not store any type information about symbols. The Object.cpp file is compiled to this object format, resulting in an Object.o file or Object.obj or what have you on your platform. When other parts of the program use this module, they almost solely trust the information that is written about it in Object.h. Nothing useful emanates out of the Object.o file except for symbols accompanied by numeric information like their offsets and sizes. If the information in the header doesn't correctly reflect Object.obj, you have undefined behavior (mitigated, in some cases, by C++'s support for function overloading, which turns mismatched function calls into unresolving symbols, thanks to name mangling).

文本包含允许c++编译器与不存储任何符号类型信息的古老对象文件格式进行互操作。对象。cpp文件被编译为这种对象格式,从而产生一个对象。o文件或对象。obj或者你的平台上有什么。当程序的其他部分使用这个模块时,它们几乎完全信任Object.h中关于它的信息。没有什么有用的东西从物体中散发出来。o文件,除了符号以外的数字信息,如它们的偏移量和大小。如果标题中的信息不能正确地反映对象。obj,您有未定义的行为(在某些情况下,由于c++对函数重载的支持,可以减轻这种行为,由于名称管理,将不匹配的函数调用转换为不解析符号)。

For instance if the header declares a variable extern int foo; but the object file is the result of compiling double foo = 0.0; it means that the rest of the program is accessing a double object as an int. What prevents this from happening is that Object.cpp includes its own header (thereby forcing the mismatch between the declaration and definition to be caught by the compiler) and that you have a sane build system in place which ensures that Object.cpp is rebuilt if anything touches Object.h. If that check is based on timestamps, you must also have a sane file system and version control system that don't do wacky things with timestamps.

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

#2


3  

If you want one interface and multiple implementations in the same program then you use an abstract virtual base.

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

Like so:

像这样:

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

Then you can have implementations:

然后可以实现:

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

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

On the other hand, if you are looking at code which implements OS independence, it might have several subdirectories, one for each OS. The header files are the same, but the source files for Windows are only built for Windows and the source files for Linux/POSIX are only built for Linux.

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

#3


1  

If you define Object::method in two different places, then how will the compiler know which one to call?

如果在两个不同的位置定义Object::方法,那么编译器如何知道调用哪个?

It won't, and in fact you will be breaking the "One Definition Rule" if you do this, which results in undefined behavior, no diagnostic required, according to the standards.

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

If you want to define multiple implementations for a class interface, you should use inheritance in some way.

如果您想为类接口定义多个实现,那么应该以某种方式使用继承。

One way that you might do it is, use a virtual base class and override some of the methods in different subclasses.

一种方法是使用虚拟基类并覆盖不同子类中的一些方法。

If you want to manipulate instances of the class as value types, then you can use the pImpl idiom, combined with virtual inheritance. So you would have one class, the "pointer" class, which exposes the interface, and holds a pointer to an abstract virtual base class type. Then, in the .cpp file, you would define the virtual base class, and define multiple subclasses of it, and different constructors of the pImpl class would instantiate different of the subclasses as the implementation.

如果希望将类的实例作为值类型进行操作,那么可以使用pImpl习语,并结合虚拟继承。因此,您将有一个类,“指针”类,它公开接口,并保存指向抽象虚拟基类类型的指针。然后,在.cpp文件中,您将定义虚拟基类,并定义它的多个子类,pImpl类的不同构造函数将实例化不同的子类作为实现。

If you want to use static polymorphism, rather than run-time polymorphism, you can use the CRTP idiom (which is still ultimately based on inheritance, just not virtual inheritance).

如果您希望使用静态多态性,而不是运行时多态性,您可以使用CRTP习惯用法(它仍然是基于继承,而不是虚拟继承)。