3

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

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

我的方法有什么危险吗?

有没有性能损失? (库的主要关注点是性能——真实的代码有明确的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

4 回答 4

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属于派生类,该类应该有自己的新标头,其中包括现有的标头。

于 2013-08-19T02:24:22.757 回答
1

标题中的代码Component.h将无法编译,除非您也#include Entity.h. Component.h如果您曾经尝试#include Component.h分开,这将导致神秘的错误;进行更改Entity.h,使其不包含完整的定义Entity;或更改Library.h以使其不再包含#include Entity.h. 这通常被认为是不好的做法,因为对于未来的代码维护者来说,这个错误很难理解。

铿锵声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 文件时立即发现)。

于 2013-08-18T12:49:10.680 回答
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(); }
于 2013-08-20T10:45:14.457 回答
0

我建议将实现放在“* .inl”中

// 实体.h

#ifndef ENTITY_H
#define ENTITY_H

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

#include "Entity.inl" 

#endif

// 实体.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

// 组件.inl

#ifndef COMPONENT_INL
#define COMPONENT_INL

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

#endif
于 2013-08-18T12:45:09.257 回答