簡體   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