[英]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
這足以宣告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(); }
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.