C++ 设计问题:具有各种抽象基础的映射 类
C++ Design issues: Map with various abstract base classes
我遇到了设计问题,需要一些外部输入。我试图避免 abstract base class 转换(因为我听说这很糟糕)。
问题出在这个结构上:
class entity... (base with pure virtual functions)
class hostile : public entity... (base with pure virtual functions)
class friendly : public entity... (base with pure virtual functions)
// Then further derived classes using these last base classes...
最初我以为我会侥幸逃脱:
const enum class FactionType : unsigned int
{ ... };
std::unordered_map<FactionType, std::vector<std::unique_ptr<CEntity>>> m_entitys;
而且...我做到了,但这给我带来了问题,因为我需要访问 "unique" 函数,特别是敌对或友好的。
我可耻地尝试过(有效但不喜欢它也不觉得安全):
// For-Each Loop: const auto& friendly : m_entitys[FactionType::FRIENDLY]
CFriendly* castFriendly = static_cast<CFriendly*>(&*friendly);
我 hoping/trying 维护 unordered_map
设计,该设计使用 FactionType
作为基本抽象 class 类型的键...无论如何,非常感谢输入.
如有语法错误,敬请谅解。
关于选角我同意@rufflewind。演员表意味着不同的东西,并且在不同的时间有用。
要在编译时强制一个内存区域(无论如何键入的决定发生在编译时)使用 static_cast。 T* 另一端等于 sizeof(T) 的内存量将被解释为 T,无论行为是否正确。
dynamic_cast 的决定完全在运行时做出,有时需要 RTTI(运行 时间类型信息)。它做出决定,如果可以的话,它将 return 一个空指针或一个指向 T 的有效指针。
不过,这个决定不仅仅局限于演员表的类型。使用数据结构来查找类型和方法(成员函数)强加了时间限制,与相对快速和强制的转换相比,这些时间限制是不存在的。有一种方法可以跳过数据结构,但不能跳过没有重大重构的转换(通过重大重构你可以做任何事情)。
您可以将演员表移动到实体 class 中,正确完成它们,然后将它们封装在那里。
class entity
{
// Previous code
public:
// This will be overridden in hostiles to return a valid
// pointer and nullptr or 0 in other types of entities
virtual hostile* cast_to_hostile() = 0
virtual const hostile* cast_to_hostile() const = 0
// This will be overridden in friendlies to return a valid
// pointer and nullptr or 0 in other types of entities
virtual friendly* cast_to_friendly() = 0
virtual const friendly* cast_to_friendly() const = 0
// The following helper methods are optional but
// can make it easier to write streamlined code in
// calling classes with a little trouble.
// Hostile and friendly can each implement this to return
// The appropriate enum member. This would useful for making
// decision about friendlies and hostiles
virtual FactionType entity_type() const = 0;
// These two method delegate storage of the knowledge
// of hostility or friendliness to the derived classes.
// These are implemented here as non-virtual functions
// because they shouldn't need to be overridden, but
// could be made virtual at the cost of a pointer
// indirection and sometimes, but not often a cache miss.
bool is_friendly() const
{
return entity_type() == FactionType_friendly;
}
bool is_hostile() const
{
return entity_type() == FactionType_hostile;
}
}
这个策略有好有坏,原因有很多。
优点:
概念上很简单。如果你了解多态性,这很容易理解。
它看起来与您现有的代码相似,表面上看起来与您现有的代码相似,使迁移更容易。在你的类型中编码敌意和友好是有原因的,这保留了那个原因。
您可以安全地使用 static_cast,因为所有的转换都存在于它们使用的 class 中,因此除非有效,否则通常不会被调用。
您可以return shared_ptr 或其他自定义智能指针代替原始指针。你可能应该。
这避免了完全避免转换的潜在代价高昂的重构。铸造是作为一种工具。
缺点:
概念上很简单。这并没有提供一套强大的词汇表(方法、classes 和模式)来构建一套用于构建高级类型机制的智能工具。
很可能某物是否是敌对的应该是一个数据成员或实现为一系列控制实例行为的方法。
有人可能认为这个 return 的指针传达了所有权并删除了它们。
每个调用者都必须在使用前检查指针的有效性。或者你可以添加方法来检查,但是调用者需要在强制转换之前调用方法来检查。这样的检查让 class 的用户感到惊讶,并使其更难正确使用。
它是多态性密集的。这会让对多态感到不舒服的人感到困惑。即使在今天,仍有许多人对多态感到不自在。
完全避免转换的重构是可能的。铸造是危险的,不是可以轻易使用的工具。
我遇到了设计问题,需要一些外部输入。我试图避免 abstract base class 转换(因为我听说这很糟糕)。
问题出在这个结构上:
class entity... (base with pure virtual functions)
class hostile : public entity... (base with pure virtual functions)
class friendly : public entity... (base with pure virtual functions)
// Then further derived classes using these last base classes...
最初我以为我会侥幸逃脱:
const enum class FactionType : unsigned int
{ ... };
std::unordered_map<FactionType, std::vector<std::unique_ptr<CEntity>>> m_entitys;
而且...我做到了,但这给我带来了问题,因为我需要访问 "unique" 函数,特别是敌对或友好的。
我可耻地尝试过(有效但不喜欢它也不觉得安全):
// For-Each Loop: const auto& friendly : m_entitys[FactionType::FRIENDLY]
CFriendly* castFriendly = static_cast<CFriendly*>(&*friendly);
我 hoping/trying 维护 unordered_map
设计,该设计使用 FactionType
作为基本抽象 class 类型的键...无论如何,非常感谢输入.
如有语法错误,敬请谅解。
关于选角我同意@rufflewind。演员表意味着不同的东西,并且在不同的时间有用。
要在编译时强制一个内存区域(无论如何键入的决定发生在编译时)使用 static_cast。 T* 另一端等于 sizeof(T) 的内存量将被解释为 T,无论行为是否正确。
dynamic_cast 的决定完全在运行时做出,有时需要 RTTI(运行 时间类型信息)。它做出决定,如果可以的话,它将 return 一个空指针或一个指向 T 的有效指针。
不过,这个决定不仅仅局限于演员表的类型。使用数据结构来查找类型和方法(成员函数)强加了时间限制,与相对快速和强制的转换相比,这些时间限制是不存在的。有一种方法可以跳过数据结构,但不能跳过没有重大重构的转换(通过重大重构你可以做任何事情)。
您可以将演员表移动到实体 class 中,正确完成它们,然后将它们封装在那里。
class entity
{
// Previous code
public:
// This will be overridden in hostiles to return a valid
// pointer and nullptr or 0 in other types of entities
virtual hostile* cast_to_hostile() = 0
virtual const hostile* cast_to_hostile() const = 0
// This will be overridden in friendlies to return a valid
// pointer and nullptr or 0 in other types of entities
virtual friendly* cast_to_friendly() = 0
virtual const friendly* cast_to_friendly() const = 0
// The following helper methods are optional but
// can make it easier to write streamlined code in
// calling classes with a little trouble.
// Hostile and friendly can each implement this to return
// The appropriate enum member. This would useful for making
// decision about friendlies and hostiles
virtual FactionType entity_type() const = 0;
// These two method delegate storage of the knowledge
// of hostility or friendliness to the derived classes.
// These are implemented here as non-virtual functions
// because they shouldn't need to be overridden, but
// could be made virtual at the cost of a pointer
// indirection and sometimes, but not often a cache miss.
bool is_friendly() const
{
return entity_type() == FactionType_friendly;
}
bool is_hostile() const
{
return entity_type() == FactionType_hostile;
}
}
这个策略有好有坏,原因有很多。
优点:
概念上很简单。如果你了解多态性,这很容易理解。
它看起来与您现有的代码相似,表面上看起来与您现有的代码相似,使迁移更容易。在你的类型中编码敌意和友好是有原因的,这保留了那个原因。
您可以安全地使用 static_cast,因为所有的转换都存在于它们使用的 class 中,因此除非有效,否则通常不会被调用。
您可以return shared_ptr 或其他自定义智能指针代替原始指针。你可能应该。
这避免了完全避免转换的潜在代价高昂的重构。铸造是作为一种工具。
缺点:
概念上很简单。这并没有提供一套强大的词汇表(方法、classes 和模式)来构建一套用于构建高级类型机制的智能工具。
很可能某物是否是敌对的应该是一个数据成员或实现为一系列控制实例行为的方法。
有人可能认为这个 return 的指针传达了所有权并删除了它们。
每个调用者都必须在使用前检查指针的有效性。或者你可以添加方法来检查,但是调用者需要在强制转换之前调用方法来检查。这样的检查让 class 的用户感到惊讶,并使其更难正确使用。
它是多态性密集的。这会让对多态感到不舒服的人感到困惑。即使在今天,仍有许多人对多态感到不自在。
完全避免转换的重构是可能的。铸造是危险的,不是可以轻易使用的工具。