简体   繁体   English

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

[英]Using templates with forward-declared types - safe?

I'm building an header-only library, and I've resolved some circular dependency issues by doing something similar to what the code shows. 我正在构建一个仅限标头的库,我通过执行类似于代码显示的操作解决了一些循环依赖性问题。

Basically, I create a private template implementation that allows me to use forward-declared types as if they were included and not forward-declared. 基本上,我创建了一个私有模板实现,它允许我使用前向声明的类型,就像它们被包含在内而不是前向声明的那样。

Is there anything dangerous about my approach? 我的做法有什么危险吗?

Is there any performance loss? 是否有任何性能损失? (the library's main focus is performance - the real code has explicit inline suggestions) (图书馆的主要焦点是表现 - 真实的代码有明确的inline建议)

Bonus question: is there an impact (positive or negative) on compilation time? 奖金问题: 编译时间是否有影响(正面或负面)?


// 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(); }
}

Is there anything dangerous about my approach? 我的做法有什么危险吗?

As long as the template instantiation is, in fact, deferred, not much can go wrong. 事实上,只要模板实例化是延迟的,就不会出错。 It could maybe declare intent a bit better if you prohibited an incorrect instantiation: 如果禁止不正确的实例化,它可能会更好地声明意图:

typename std::enable_if< std::is_same< T, Entity >::value
    && sizeof ( T ) /* Ensure that Entity is not incomplete. */ >::type

On second reading of your code, it looks like the non-template doA function will immediately and prematurely instantiate doAImpl , defeating the templating. 在你的代码的二读,它看起来像非模板doA功能会立即和过早实例doAImpl ,击败模板。 I don't think the public interface can be a non-template, since it must cause instantiation of whatever Impl ultimately does the work, but only when the function is actually used. 我不认为公共接口可以是非模板,因为它必须导致Impl最终完成工作的实例化,但仅在实际使用该函数时。 Unless there's another layer of templating protecting the user, it's probably better to do away with the private part and do everything in doA . 除非有另一层模板保护用户,否则最好不要使用private部分并在doA执行所有操作。

Is there any performance loss? 是否有任何性能损失?

Nope. 不。 The function is sure to be inlined either way. 该功能肯定以任何方式内联。

Is there an impact (positive or negative) on compilation time? 编译时间是否有影响(正面或负面)?

The minuscule added complexity will certainly not make a difference. 微不足道的增加的复杂性肯定不会有所作为。


The only reason not to do this is obvious: it's ugly. 不这样做的唯一原因是显而易见的:它很难看。 And quite likely a violation of separation of concerns. 很可能违反了关注点的分离。

One workaround would be to use a non-member, free function instead. 一种解决方法是使用非成员免费功能。

struct Entity;
void a( Entity & );

    void doA() { a( entity ); }

Another would be to simply treat Entity.h or whatever as a dependency and include it. 另一种方法是简单地将Entity.h或其他任何东西视为依赖项并包含它。 I think this would be the most popular solution. 我认为这将是最受欢迎的解决方案。

If Component is really not dependent on Entity , then maybe doA belongs in a derived class which should have its own new header, which includes both the existing ones. 如果Component实际上不依赖于Entity ,则doA可能属于派生类,该派生类应具有自己的新头,其中包括现有头。

The code in the header Component.h will fail to compile unless you also #include Entity.h . 除非你还#include Entity.h否则标题Component.h的代码将无法编译。 This will result in mysterious errors in Component.h if you were ever to try to #include Component.h separately; 如果您曾尝试单独#include Component.h这将导致Component.h中出现神秘错误; to change Entity.h so that it does not contain a complete definition of Entity ; 更改Entity.h ,使其不包含Entity的完整定义; or to change Library.h so that it no longer contains #include Entity.h . 或更改Library.h ,使其不再包含#include Entity.h This is generally considered bad practice as this error would be difficult to understand for a future maintainer of the code. 这通常被认为是不好的做法,因为对于未来的代码维护者来说,这个错误很难理解。

clang gives error: member access into incomplete type 'Entity' clang给出error: member access into incomplete type 'Entity'

Here's a live example demonstrating the error: http://coliru.stacked-crooked.com/view?id=d6737c6f710992cce8a3f28217562da2-25dabfc2c190f5ef027f31d968947336 这是一个演示错误的实时示例: http//coliru.stacked-crooked.com/view?id = d6737c6f710992cce8a3f28217562da2-25dabfc2c190f5ef027f31d968947336

The function doAImpl() is instantiated if it is called in a context that does not depend on a template parameter. 如果在不依赖于模板参数的上下文中调用函数doAImpl() ,则将其实例化。 At the point of instantiation, Entity is used in a class-member-access and therefore required to be complete. 在实例化时, Entity用于类成员访问,因此需要完成。 If you do not #include Entity.h , the type Entity will not be complete at the point of instantiation. 如果不#include Entity.h ,则实例化时类型Entity将不完整。

A much simpler (and prettier) way to achieve what you want is to do: 实现您想要的更简单(更漂亮)的方法是:

template<class Entity>
class Component 
{        
    Entity& entity;

    public:
        void doA() { entity.a(); } // this compiles fine
};

In general, (even in a header-only library) you can avoid a lot of headaches by following this simple rule: every header name.h must have a matching name.cpp which contains #include name.h before any other #include directive. 通常,(即使在仅头文件库中)您可以通过遵循以下简单规则来避免许多麻烦:每个头name.h必须具有匹配的name.cpp ,其中包含#include name.h然后才能执行任何其他#include指令。 This guarantees that name.h can be safely included anywhere without resulting in this kind of error. 这保证了name.h可以安全地包含在任何地方而不会导致这种错误。

This is the canonical reference, from John Lakos in Large-Scale C++ Software Design , quoted by Bruce Eckel in Thinking in C++ : http://bruce-eckel.developpez.com/livres/cpp/ticpp/v1/?page=page_18 这是大规模C ++软件设计中 John Lakos的规范参考,Bruce Eckel在Thinking in C ++中引用: http//bruce-eckel.developpez.com/livres/cpp/ticpp/v1/? page = page_18

Latent usage errors can be avoided by ensuring that the .h file of a component parses by itself - without externally-provided declarations or definitions... Including the .h file as the very first line of the .c file ensures that no critical piece of information intrinsic to the physical interface of the component is missing from the .h file (or, if there is, that you will find out about it as soon as you try to compile the .c file). 通过确保组件的.h文件自行解析,可以避免潜在的使用错误 - 没有外部提供的声明或定义......包含.h文件作为.c文件的第一行确保没有关键部分.h文件中缺少组件物理接口固有的信息(或者,如果有的话,只要您尝试编译.c文件就会发现它)。

I would suggest to put implementation in "*.inl" 我建议将实现放在“* .inl”中

// Entity.h // Entity.h

#ifndef ENTITY_H
#define ENTITY_H

class Component; // forward-declaration
struct Entity { void a(); };

#include "Entity.inl" 

#endif

// Entity.inl // Entity.inl

#ifndef ENTITY_INL
#define ENTITY_INL

#include "Component.h";
inline void Entity::a() { /* implementation using Component and Entity */}

#endif

// Component.h // 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 // Component.inl

#ifndef COMPONENT_INL
#define COMPONENT_INL

#include "Entity.h";
inline void Component::doA() { entity.a(); }

#endif

It was sufficient to declare doA in Component.h , and define it in Entity.h . 这足以宣告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