繁体   English   中英

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

[英]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

这足以宣告doAComponent.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM