C++ 使用具有正向声明类型的模板-安全?
我正在构建一个只包含标题的库,通过执行与代码所示类似的操作,我解决了一些循环依赖性问题 基本上,我创建了一个私有模板实现,它允许我使用前向声明的类型,就像它们被包含而不是被前向声明一样 我的方法有什么危险吗? 是否有性能损失?(库的主要焦点是性能-真正的代码有明确的C++ 使用具有正向声明类型的模板-安全?,c++,templates,c++11,header,forward-declaration,C++,Templates,C++11,Header,Forward Declaration,我正在构建一个只包含标题的库,通过执行与代码所示类似的操作,我解决了一些循环依赖性问题 基本上,我创建了一个私有模板实现,它允许我使用前向声明的类型,就像它们被包含而不是被前向声明一样 我的方法有什么危险吗? 是否有性能损失?(库的主要焦点是性能-真正的代码有明确的内联建议) 额外问题:对编译时间有影响吗(正面还是负面)? //Component.h 结构实体;//远期申报 类组件 { 实体&实体; 模板void doimpl(){static_cast(entity).a(
内联建议)
额外问题:对编译时间有影响吗(正面还是负面)?
//Component.h
结构实体;//远期申报
类组件
{
实体&实体;
模板void doimpl(){static_cast(entity).a();}
公众:
//void notWorking(){entity.a();}我建议将实现放在“*.inl”中
//实体.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
//第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
标题Component.h
中的代码将无法编译,除非您还#include Entity.h
。这将导致Component.h
中出现神秘错误,如果您试图单独包含Component.h
;更改Entity.h
,使其不包含的完整定义>实体
;或更改库.h
,使其不再包含#包含实体.h
。这通常被认为是不好的做法,因为此错误对于未来的代码维护者来说很难理解
clang给出错误:成员访问不完整类型的“实体”
下面是一个演示错误的实例:
函数doimpl()如果在不依赖模板参数的上下文中调用
,则会实例化它。在实例化时,实体
用于类成员访问,因此需要完整。如果不#包含Entity.h
,则类型
实体
在实例化时将不完整
实现您想要的目标的一个更简单(更漂亮)的方法是:
template<class Entity>
class Component
{
Entity& entity;
public:
void doA() { entity.a(); } // this compiles fine
};
模板
类组件
{
实体&实体;
公众:
void doA(){entity.a();}//这可以很好地编译
};
通常,(即使在仅页眉的库中)遵循这个简单的规则可以避免很多麻烦:每个头name.h
必须在任何其他\include
指令之前有一个匹配的name.cpp
,它包含\include name.h
。这保证了name.h
可以安全地包含在任何地方,而不会导致此类错误
这是John Lakos在C++软件设计中引用的经典参考文献,引用Bruce Eckel在C++中的思想:
通过确保组件的.h文件自行解析(无需外部提供的声明或定义),可以避免潜在的使用错误…将.h文件作为.c文件的第一行,可以确保.h文件中没有缺少组件物理接口固有的关键信息(或者,如果有,您将在尝试编译.c文件时尽快了解它)
我的方法有什么危险吗?
事实上,只要模板实例化是延迟的,就不会出现太多错误。如果禁止错误的实例化,它可能会更好地声明意图:
typename std::enable_if< std::is_same< T, Entity >::value
&& sizeof ( T ) /* Ensure that Entity is not incomplete. */ >::type
另一种方法是简单地将Entity.h
或任何东西作为依赖项,并将其包括在内。我认为这将是最流行的解决方案
如果组件
确实不依赖于实体
,那么doA
可能属于一个派生类,它应该有自己的新头,其中包括两个现有头。在组件.h
中声明doA
,并在实体.h
中定义它就足够了
我做了一些类似的事情,但我没有打破使用前向声明类型可以做什么的规则。至于“性能影响”,没有。我甚至不知道为什么会有。如果你的“关注性能”的想法是“添加内联
”,那么我就看到了这个目标的暗淡前景。好吧,那么请原谅我的热情。尽管我想知道-如果你知道这些事情,你不应该也知道解决性能问题最可靠的方法是查看汇编代码和/或基准测试吗?;-)在我看来,如果您的调用站点在调用doA()之前包含Entity.h
,那么您只能明智地使用此构造
。如果是这样,那么您最好直接包含该文件。您可以通过将函数定义添加到类定义之外来打破循环依赖关系。每当人们开始编写“游戏引擎”,出于性能考虑,常识不再适用。只需首先关注正确性和逻辑依赖性。如果您使用pimpl
进行编译加速,并使用模板参数编写抽象接口,通常编译改进的空间不大。它同时使用clang++3.4
和g++4.8.1
。如问题中的一条评论所述,这项工作的要求是呼叫站点包括Entity.h
@VittorioRomeo您可能需要仔细检查一下。我确信它可以工作。我的库由组件.h
实体.h
和管理器.h
组成。它还有一个>LIBRARY.h
主要包含在外部项目中。LIBRARY.h
只包含组件.h
、实体.h
和管理器.h
。在外部项目中,当我编写包含时,我可以使用template<class Entity>
class Component
{
Entity& entity;
public:
void doA() { entity.a(); } // this compiles fine
};
typename std::enable_if< std::is_same< T, Entity >::value
&& sizeof ( T ) /* Ensure that Entity is not incomplete. */ >::type
struct Entity;
void a( Entity & );
void doA() { a( entity ); }
// Component.h
class Entity;
class Component
{
void doA();
}
// Entity.h
class Entity { ... }
// still in Entity.h
void Component::doA() { entity.a(); }