我正在开发一个相当大的项目(3D图形引擎),并且在重构代码时遇到了一些麻烦。我希望我的所有类都在单个文件中实现(只有.hpp,而不是每个类都有.cpp和.hpp文件)。除了想要这样做之外,我没有特定的理由这样做,但我希望避免讨论C ++最佳实践。
当我这样做时,我得到一系列多重定义错误,如下所示:
/tmp/ccztDQam.o: In function `Point3DH::normalize()':
Renderer.cpp:(.text+0x736): multiple definition of `Point3DH::normalize()'
/tmp/ccawpiuU.o:main.cpp:(.text+0x1a6de): first defined here
/tmp/ccztDQam.o: In function `Point3DH::dot(Point3DH, Point3DH)':
Renderer.cpp:(.text+0x79e): multiple definition of `Point3DH::dot(Point3DH, Point3DH)'
/tmp/ccawpiuU.o:main.cpp:(.text+0x1a746): first defined here
/tmp/ccztDQam.o: In function `Point3DH::cross(Point3DH, Point3DH)':
Renderer.cpp:(.text+0x7d6): multiple definition of `Point3DH::cross(Point3DH, Point3DH)'
/tmp/ccawpiuU.o:main.cpp:(.text+0x1a77e): first defined here
...
当类开始包含彼此并且代码重复多次时,问题就出现了。似乎this答案中解释的标题保护不够。我想知道是否有办法绕过这个或另一种方法来实现目标。
项目被组织成几何或多边形等模块(文件夹),其中包含相关的类,因此包含路径转到父目录,然后转到正确的模块和类
供参考,以下是其中一个文件(./graphics/Raster.hpp):
#ifndef GRAPHICS_RASTER
#define GRAPHICS_RASTER
#include "../graphics/Colour.hpp"
#include <vector>
class Raster {
private:
std::vector<Colour> image;
std::vector<double> zBuffer;
int width;
int height;
public:
Raster(int, int, Colour);
void setPixel(int, int, double, Colour);
int getWidth();
int getHeight();
};
#endif
#ifndef GRAPHICS_RASTER_IMPLEMENTATION
#define GRAPHICS_RASTER_IMPLEMENTATION
#include "../graphics/Colour.hpp"
#include <vector>
#include <limits>
Raster::Raster(int width, int height, Colour clear) :
image(std::vector<Colour>(width*height, clear)),
zBuffer(std::vector<double>(width*height, -std::numeric_limits<double>::max())),
width(width),
height(height)
{}
void Raster::setPixel(int x, int y, double z, Colour c) {
if(x < 0 || x >= width || y < 0 || y >= height) return;
if(z <= zBuffer[(height - y - 1)*width + x]) return;
image[(height - y - 1)*width + x] = c;
zBuffer[(height - y - 1)*width + x] = z;
}
int Raster::getWidth() {return width;}
int Raster::getHeight() {return height;}
#endif
答案 0 :(得分:4)
如果由于某种原因想要在头文件中实现所有内容,则必须将所有函数内联。类定义中定义的函数是隐式内联的。必须使用inline
关键字明确声明类外定义的函数。
这就是您在标题的“实现”部分中使用的每个定义所要做的 - 为每个函数定义添加显式inline
关键字。 E.g。
inline void Raster::setPixel(int x, int y, double z, Colour c) {
if(x < 0 || x >= width || y < 0 || y >= height) return;
if(z <= zBuffer[(height - y - 1)*width + x]) return;
image[(height - y - 1)*width + x] = c;
zBuffer[(height - y - 1)*width + x] = z;
}
等等。
当然,您也可以将所有成员函数定义移动到类定义中(这将使它们成为内联),但这将排除您现在拥有的这种明显分离的两节标题结构。我不知道这对你有多重要。
答案 1 :(得分:2)
每次将标头包含在cpp文件中时,都会创建一个新的实现副本。
您需要确保实现仅在一个cpp文件中使用 - 或者内联每个方法。
答案 2 :(得分:1)
本指南对此有很好的想法:
https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
的示例:
https://github.com/nothings/stb
基本上:
1 - 制作 #define UNIQUE_NAME_IMP 和 #define UNIQUE_NAME_HEADER ,使用以下命令在不同文件上显示实施和声明:
您的实施:
#ifdef _DECL_
type declaration
function prototype
#endif
#ifdef _IMPL_
code
#endif
并在另一个将使用它的文件中:
#define _DECL_
#include <my_header.h>
code...
...
//use this only once to avoid
//duplicate symbol like you mentioned in your post.
#define _IMPL_
#include <my_header.h>
2 - 避免内存分配,让你的函数使用你传递给你的结构的内存。
3 - 避免外部依赖。在使用标题之前,每个依赖项都会使您使用标记或创建要求...
4次使用“静态”。这使得实现对创建它的源文件是私有的。