我做了一件阴暗的事情

(看似)阴暗的东西是否因实际原因而被接受? 首先,我的代码背景。我正在编写2D游戏的图形模块。我的模块包含两个以上的类,但我在这里只提两个:Font和GraphicsRenderer。 Font提供了一个接口,通过它可以加载(和释放)文件,而不是更多。在我的字体标题中,我不希望泄漏任何实现细节,包括我正在使用的第三方库的数据类型。我阻止第三方lib在标题中可见的方式是通过一个不完整的类型(我明白这是标准做法):
class Font
{
  private:
    struct FontData;
    boost::shared_ptr<FontData> data_;
};
GraphicsRenderer是(read:singleton)设备,用于初始化和完成第三方图形库,还用于渲染图形对象(如字体,图像等)。它是单身的原因是因为,正如我所说,该类自动初始化第三方库;它在创建单例对象时执行此操作,并在单例销毁时退出库。 无论如何,为了使GR能够呈现Font,它显然必须能够访问其FontData对象。一种选择是拥有一个公共getter,但这会暴露Font的实现(除了Font和GR之外,其他任何类都不应该关心FontData)。相反,我认为让GR成为Font的朋友更好。 注意:到目前为止,我已经做了两件事,有些人可能会认为这是阴暗的(单身人士和朋友),但这些并不是我想问你的事情。尽管如此,如果您认为我将GR作为单身人士和Font的朋友的理由是错误的,请批评我并提供更好的解决方案。 阴暗的事情。因此GR可以访问Font :: data_虽然是友谊,但是它如何确切地知道FontData是什么(因为它没有在标题中定义,它是一个不完整的类型)?我只会展示包含基本原理的代码和评论......
// =============================================================================
//   graphics/font.cpp
// -----------------------------------------------------------------------------

struct Font::FontData
    : public sf::Font
{
    // Just a synonym of sf::Font
};

// A redefinition of FontData exists in GraphicsRenderer::printText(),
// which will have to be modified as well if this definition is modified.
// (The redefinition is called FontDataSurogate.)
// Why not have FontData defined only once in a separate header:
// If the definition of FontData changes, most likely printText() text will
// have to be altered also regardless. Considering that and also that FontData
// has (and should have) a very simple definition, a separate header was
// considered too much of an overhead and of little practical advantage.


// =============================================================================
//   graphics/graphics_renderer.cpp
// -----------------------------------------------------------------------------

void GraphicsRenderer::printText(const Font& fnt /* ... */)
{
    struct FontDataSurogate
        : public sf::Font {
    };

    FontDataSurogate* suro = (FontDataSurogate*)fnt.data_.get();
    sf::Font& font = (sf::Font)(*suro);

    // ...
}
所以这是我想要做的阴暗的事情。基本上我想要的是对我的理由的回顾,所以请告诉我,如果你认为我做了一些可怕的事情,或者如果没有确认我的理由,那么我可以有点确定我正在做正确的事情。 :)(这是我最大的项目,我只是在开始时所以我有点感觉黑暗中的东西。)     
已邀请:
一般来说,如果某些事情看起来很粗略,我发现它经常值得回去几次并试图弄清楚为什么这是必要的。在大多数情况下,会出现某种修复方式(可能不是“好”,但不依赖于任何类型的技巧)。 现在,我在您的示例中看到的第一个问题是这段代码:
struct FontDataSurogate
    : public sf::Font {
};
在不同的文件中出现两次(既不是标题)。当你改变一个而不是另一个时,可能会回来并且很麻烦,并且确保两者相同将很可能是一种痛苦。 为了解决这个问题,我建议将定义放在
FontDataSurogate
和相应的包含(无论库/标题定义
sf::Font
)在一个单独的标题中。从需要使用two3ѭ的两个文件中,包括该定义标题(不是来自任何其他代码文件或标题,只是那两个)。 如果你有一个库的主类声明头,那么在那里放置类的前向声明,并在对象和参数(常规指针或共享指针)中使用指针。 然后,您可以使用
friend
或添加get方法来检索数据,但是通过将类定义移动到其自己的标头,您已经创建了该代码的单个副本,并且具有与其他库连接的单个对象/文件。 编辑: 你在我写这篇文章的时候就这个问题发表了评论,所以我会在你的评论中添加回复。   “太多的开销” - 更多的文档,还有一件事要包括,代码的复杂性增长等等。 不是这样。与现在必须保持相同的两个代码相比,您将拥有一个代码副本。代码以任何一种方式存在,因此需要记录,但您的复杂性和特别是维护得以简化。你确实获得了两个
#include
的陈述,但成本如此之高?   “实用性很小” - 每次修改FontData时都必须修改printText(),无论它是否在单独的标题中定义。 优点是重复代码更少,使您(和其他人)更容易维护。在输入数据发生变化时修改功能实际上并不令人惊讶或不寻常。将它移动到另一个标题不会花费你任何东西,但提到的包括。     
friend
很好,鼓励。有关详细信息,请参阅C ++ FAQ Lite的基本原理:朋友是否违反了封装? 这条线确实很可怕,因为它调用了未定义的行为:
FontDataSurogate* suro = (FontDataSurogate*)fnt.data_.get();
    
你转发声明了
FontData
结构的存在,然后继续在两个位置完全声明它:Font和GraphicsRenderer。 EW。现在你必须手动保持这些二进制兼容。 我确信它有效,但你是对的,它有点阴暗。但是,无论何时我们说这样的事情都是eeevil,我们的意思是避免某种做法,但需要注意的是,它有时会有用。话虽如此,我认为这不是其中之一。 一种技术是颠倒你的处理。而不是将所有逻辑放在GraphicsRenderer中,而是将其中的一些放在Font中。像这样:
class Font
{
  public:
    void do_something_with_fontdata(GraphicsRenderer& gr);

  private:
    struct FontData;
    boost::shared_ptr<FontData> data_;
};

void GraphicsRenderer::printText(const Font& fnt /* ... */)
{
   fnt.do_something_with_fontdata(*this);
}
这样,Font详细信息保存在Font类中,甚至GraphicsRenderer也不需要知道实现的细节。这也解决了
friend
问题(虽然我认为朋友不是那么糟糕)。 根据代码的布局方式以及代码的执行情况,尝试将其反转可能非常困难。如果是这种情况,只需将
FontData
的真实声明移动到其自己的头文件中,并在
Font
GraphicsRenderer
中使用它。     
你花了很多精力问这个问题然后你应该通过复制代码来节省。 您说明了您不想添加文件的三个原因: 额外包括 额外文档 额外的复杂性 但我不得不说通过复制代码来增加2和3。现在,您将记录它在原始位置所做的事情,以及它在代码库中另一个随机位置再次定义的炒猴子。复制代码只会增加项目的复杂性。 您唯一保存的是包含文件。但文件很便宜。你不应该害怕创造它们。几乎没有成本(或至少应该有)添加新的头文件。 这样做的好处是: 编译器不必使您提供的定义兼容 有一天,有人会修改FontData类而不修改PrintText(),也许他们应该修改PrintText(),但他们要么还没有完成它,要么不知道他们需要。或者也许是以一种简单的方式对FontData上的其他数据没有意义。无论如何,不​​同的代码片段将在不同的假设下运行,并且会在非常难以追踪的bug中爆炸。     

要回复问题请先登录注册