使用具有前向声明类型的模板 - 安全吗?

时间:2013-08-18 12:06:52

标签: c++ templates c++11 header forward-declaration

我正在构建一个仅限标头的库,我通过执行类似于代码显示的操作解决了一些循环依赖性问题。

基本上,我创建了一个私有模板实现,它允许我使用前向声明的类型,就像它们被包含在内而不是前向声明的那样。

我的做法有什么危险吗?

是否存在任何性能下降? (该库的主要关注点是性能 - 真正的代码有明确的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(); }
}

4 个答案:

答案 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(); }
相关问题