[英]Using templates with forward-declared types - safe?
我正在构建一个仅限标头的库,我通过执行类似于代码显示的操作解决了一些循环依赖性问题。
基本上,我创建了一个私有模板实现,它允许我使用前向声明的类型,就像它们被包含在内而不是前向声明的那样。
我的做法有什么危险吗?
是否有任何性能损失? (图书馆的主要焦点是表现 - 真实的代码有明确的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(); }
}
我的做法有什么危险吗?
事实上,只要模板实例化是延迟的,就不会出错。 如果禁止不正确的实例化,它可能会更好地声明意图:
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
可能属于派生类,该派生类应具有自己的新头,其中包括现有头。
除非你还#include Entity.h
否则标题Component.h
的代码将无法编译。 如果您曾尝试单独#include Component.h
这将导致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
可以安全地包含在任何地方而不会导致这种错误。
这是大规模C ++软件设计中 John Lakos的规范参考,Bruce Eckel在Thinking in C ++中引用: http : //bruce-eckel.developpez.com/livres/cpp/ticpp/v1/? page = page_18
通过确保组件的.h文件自行解析,可以避免潜在的使用错误 - 没有外部提供的声明或定义......包含.h文件作为.c文件的第一行确保没有关键部分.h文件中缺少组件物理接口固有的信息(或者,如果有的话,只要您尝试编译.c文件就会发现它)。
我建议将实现放在“* .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
这足以宣告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(); }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.