我正在构建一个仅限标头的库,我通过执行类似于代码显示的操作解决了一些循环依赖性问题。
基本上,我创建了一个私有模板实现,它允许我使用前向声明的类型,就像它们被包含在内而不是前向声明的那样。
我的做法有什么危险吗?
是否存在任何性能下降? (该库的主要关注点是性能 - 真正的代码有明确的inline
建议)
奖金问题:是否会对编制时间产生影响(正面或负面)?
// Entity.h
#include "Component.h"
struct Entity { void a() { ... } }
// Component.h
struct Entity; // forward-declaration
class Component
{
Entity& entity;
template<class T = Entity> void doAImpl() { static_cast<T&>(entity).a(); }
public:
// void notWorking() { entity.a(); } <- this does not compile
void doA() { doAImpl(); }
}
答案 0 :(得分:3)
我的做法有什么危险吗?
只要模板实例化实际上是延迟的,就不会出错。如果您禁止不正确的实例化,它可能会更好地声明意图:
typename std::enable_if< std::is_same< T, Entity >::value
&& sizeof ( T ) /* Ensure that Entity is not incomplete. */ >::type
在二读你的代码时,看起来非模板doA
函数会立即过早地实例化doAImpl
,从而打败了模板。我不认为公共接口可以是非模板,因为它必须导致实例化Impl
最终完成工作,但仅在实际使用该函数时。除非有另一层模板保护用户,否则最好不要使用private
部分并在doA
中执行所有操作。
有任何性能损失吗?
不。该功能肯定以任何方式内联。
编译时是否会产生影响(正面或负面)?
微不足道的复杂性肯定不会有所作为。
不这样做的唯一原因是显而易见的:它很难看。很可能违反了关注点分离。
一种解决方法是使用非成员免费功能。
struct Entity;
void a( Entity & );
void doA() { a( entity ); }
另一种方法是简单地将Entity.h
或其他任何东西视为依赖项并包含它。我认为这将是最受欢迎的解决方案。
如果Component
实际上不依赖于Entity
,则doA
可能属于派生类,该派生类应具有自己的新标头,其中包括现有标头。
答案 1 :(得分:1)
除非您Component.h
,否则标题#include Entity.h
中的代码将无法编译。如果您曾单独尝试Component.h
,这将导致#include Component.h
中出现神秘错误;更改Entity.h
,使其不包含Entity
的完整定义;或更改Library.h
以使其不再包含#include Entity.h
。这通常被认为是不好的做法,因为对于未来的代码维护者来说,这个错误很难理解。
clang给出error: member access into incomplete type 'Entity'
以下是演示错误的实时示例:http://coliru.stacked-crooked.com/view?id=d6737c6f710992cce8a3f28217562da2-25dabfc2c190f5ef027f31d968947336
如果在不依赖于模板参数的上下文中调用函数doAImpl()
,则将其实例化。在实例化时,Entity
用于类成员访问,因此需要完成。如果您没有#include Entity.h
,则在实例化时类型Entity
将无法完成。
实现您想要的更简单(更漂亮)的方法是:
template<class Entity>
class Component
{
Entity& entity;
public:
void doA() { entity.a(); } // this compiles fine
};
通常,(即使在仅限标题的库中)您可以通过遵循以下简单规则来避免很多麻烦:每个标头name.h
必须包含name.cpp
,其中包含#include name.h
在任何其他#include
指令之前。这可以保证name.h
可以安全地包含在任何地方而不会导致此类错误。
这是John Lakos在大规模C ++软件设计中的规范性引用,引自Bruce Eckel的 Thinking in C ++ :http://bruce-eckel.developpez.com/livres/cpp/ticpp/v1/?page=page_18
通过确保组件的.h文件自行解析,可以避免潜在的使用错误 - 没有外部提供的声明或定义......包含.h文件作为.c文件的第一行确保.h文件中没有缺少组件物理接口固有的关键信息(或者,如果有的话,只要您尝试编译.c文件就会发现它。)
答案 2 :(得分:0)
我建议将实现放在“* .inl”
中// Entity.h
#ifndef ENTITY_H
#define ENTITY_H
class Component; // forward-declaration
struct Entity { void a(); };
#include "Entity.inl"
#endif
// Entity.inl
#ifndef ENTITY_INL
#define ENTITY_INL
#include "Component.h";
inline void Entity::a() { /* implementation using Component and Entity */}
#endif
// Component.h
#ifndef COMPONENT_H
#define COMPONENT_H
struct Entity; // forward-declaration
class Component
{
Entity& entity;
public:
void doA();
};
#include "Component.inl"
#endif
// Component.inl
#ifndef COMPONENT_INL
#define COMPONENT_INL
#include "Entity.h";
inline void Component::doA() { entity.a(); }
#endif
答案 3 :(得分:0)
在doA
中声明Component.h
并在Entity.h
中定义它就足够了。
// Component.h
class Entity;
class Component
{
void doA();
}
// Entity.h
class Entity { ... }
// still in Entity.h
void Component::doA() { entity.a(); }