我正在研究仅标头库的代码库。它包含这个Polygon类,它具有很大的问题:大约8000行。我正试图将其分解,但是遇到了麻烦。该类和库的几个约束:


我不能随意更改库以要求预编译的部分。这不适合我们当前的构建街道,人们强烈认为它仅是标题。
该类对性能至关重要,它的分配和算法占我正在处理的应用程序总运行时的99%以上。
有时,此类经常被构造(很多三角形),并且经常调用其​​方法。因此,如果可能的话,我希望它没有虚拟表,也没有追逐指针以进行合成,除非保证编译器(GCC -O2)对此进行了优化。


此类包含对公共功能(例如area()contains(Point2))中的多边形的若干操作。这些中的每一个都有针对各种用例的几种实现,主要是小多边形与大型多边形,其中小多边形采用幼稚的单线程方法,而大型多边形则运行多线程或使用时间复杂度更高的算法。基本上像这样(简化):

class Polygon {
public:
    area_t area() {
        if(size() < 150) return area_single_thread();
        return area_openmp();
    }

    bool contains(Point2 point) {
        if(size() < 75) return contains_single_thread(point);
        if(size() < 6000) return contains_openmp(point);
        return contains_opencl(point);
    }
    ...

private:
    area_t area_single_thread() { ... }
    area_t area_openmp() { ... }
    bool contains_single_thread(Point2 point) { ... }
    bool contains_openmp(Point2 point) { ... }
    bool contains_opencl(Point2 point) { ... }
    ...
}


我的尝试是将所有这些操作放到一个单独的文件中。这似乎是逻辑上的关注分离,并使代码更具可读性。

到目前为止,我最好的尝试是这样的:

//polygon.hpp
class Polygon {
public:
    area_t area() {
        if(size() < 150) return area_single_thread();
        return area_openmp();
    }

    bool contains(Point2 point) {
        if(size() < 75) return contains_single_thread(point);
        if(size() < 6000) return contains_openmp(point);
        return contains_opencl(point);
    }
    ...

private:
//Private implementations separated out to different files for readability.
#include "detail/polygon_area.hpp"
#include "detail/polygon_contains.hpp"
...
}


//polygon_area.hpp
area_t area_single_thread() { ... }
area_t area_openmp() { ... }


//polygon_contains.hpp
bool contains_single_thread(Point2 point) { ... }
bool contains_openmp(Point2 point) { ... }
bool contains_opencl(Point2 point) { ... }


但是,这样做的主要缺点是这些子文件不是完整的头文件。它们包含类的一部分,并且永远不应包含在Polygon类之外。这不是灾难性的,但是肯定在一年后很难理解。

我研究过的替代方法:


Mixins。但是,mixin则无法访问基类中的数据。
自由浮动功能类似于Boost的功能。但是,这有几个问题:自由浮动功能无法访问受保护的字段。这些文件需要相互包含,导致当自由浮动功能需要Polygon类时,它们是不完整的类型。需要提供指向多边形的指针(不确定是否会优化吗?)。
提供实现类的模板参数。这最终类似于自由浮动函数,因为实现类需要访问Polygon的受保护字段,Polygon在实现需要时是不完整的,并且仍然需要以某种方式提供Polygon实施。
我曾想过要通过继承来实现这一点,其中受保护的数据成员位于私有基类中。子类是详细的实现。然后将有一个公共类,其所有公共功能仍可以调用detail实现。但是,这是刻板的钻石问题,因此需要一个虚拟表。但是没有测试,因为这很难设置。


您认为最好的解决方案是什么?您知道我可以尝试的其他选择吗?

最佳答案

我相信您可以使用“好奇地重复出现的模板模式”(也称为“静态多态性”)。这是一篇很好的文章,介绍了为什么它不是未定义的行为Why is the downcast in CRTP defined behaviour

-

我用一行代码稍微简化了您的示例。这是基类,在这种情况下,它是长度计算功能的实现:

template <typename T>
class LineLength
{
    // This is for non-const member functions
    T & Base(){ return *static_cast<T *>(this); }
    // This is for const member functions
    T const & Base() const { return *static_cast<T const *>(this); }
public:
    float Length() const
    {
        return Base().stop - Base().start;
    }
};


-

这是主类,它继承自基类并引入Length函数。请注意,要使LineLength访问受保护的成员,它需要成为朋友。如果需要外部函数来访问LineLength,则必须公开继承LineLength。

class Line : public LineLength<Line>
{
protected:
    friend class LineLength<Line>;
    float start, stop;
public:
    Line(float start, float stop): start{start}, stop{stop} {}
};


然后使用以下命令运行它:

int main()
{
    Line line{1,3};
    return line.Length();
}


该示例可以在此处在线运行:https://onlinegdb.com/BJssU3TUr
以及带有单独标题的实现的版本:https://onlinegdb.com/ry07PnTLB

-

如果您需要访问基类函数,则可以执行类似的操作。

class Line : public LineLength<Line>
{
protected:
    friend class LineLength<Line>;
    float start, stop;
public:
    Line(float start, float stop): start{start}, stop{stop} {}

    void PrintLength() const
    {
        std::cout << LineLength<Line>::Length() << "\n";
    }
};


注意,在类中,您需要通过基本类型(即LineLength :: Length())来评估基本成员函数。

编辑

如果需要使用非常量成员函数,则必须提供Base()函数的非常量重载。

基类的一个示例可以是Collapser。
此功能将停止变量设置为开始变量。

template <typename T>
class Collapser
{
    // This is for non-const member functions
    T & Base(){ return *static_cast<T *>(this); }
public:
    void Collapse()
    {
        Base().stop = Base().start;
    }
};


要使用此代码,请按照应用LineLength的相同方式将其应用于类。

class Line : public LineLength<Line>, public Collapser<Line>
{
protected:
    friend class Collapser<Line>;
    friend class LineLength<Line>;
    float start, stop;
public:
    Line(float start, float stop): start{start}, stop{stop} {}
};

关于c++ - 在仅 header 库中拆分大文件,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57966055/

10-15 04:31