use*_*414 25 c++ pointers reference
在许多地方你可以读到这dynamic_cast意味着"糟糕的设计".但我找不到任何具有适当用法的文章(显示出良好的设计,而不仅仅是"如何使用").
我正在编写一个带有电路板的棋盘游戏和许多不同类型的卡,这些卡具有许多属性(一些卡可以放在板上).所以我决定将其分解为以下类/接口:
class Card {};
class BoardCard : public Card {};
class ActionCard : public Card {};
// Other types of cards - but two are enough
class Deck {
Card* draw_card();
};
class Player {
void add_card(Card* card);
Card const* get_card();
};
class Board {
void put_card(BoardCard const*);
};
Run Code Online (Sandbox Code Playgroud)
有些人建议我只使用一个描述卡片的课程.但我的意思是许多相互排斥的属性.对于Board类' put_card(BoardCard const&)- 它是接口的一部分,我不能把任何卡放在板上.如果我只有一种类型的卡,我将不得不在方法内检查它.
我看到的流程如下:
所以我dynamic_cast在把卡片放在板子上之前使用.我认为在这种情况下使用一些虚拟方法是不可能的(另外我没有任何意义为每张卡添加一些关于板的操作).
所以我的问题是:我的设计是什么?我怎么能避免dynamic_cast?使用某些类型属性和ifs将是一个更好的解决方案......?
PS dynamic_cast在设计环境中处理使用的任何来源都是值得赞赏的.
mik*_*osz 13
是的,dynamic_cast是一种代码味道,但是添加的功能也是如此,这些功能试图让它看起来像你有一个很好的多态接口,但实际上是等同于dynamic_cast类似的东西can_put_on_board.我甚至会说这can_put_on_board更糟糕 - 你正在复制代码,否则dynamic_cast会使界面变得混乱.
与所有代码气味一样,它们应该让你警惕,并不一定意味着你的代码是坏的.这一切都取决于你想要达到的目标.
如果你正在实现一个有5k行代码,两类卡片的棋盘游戏,那么任何可行的东西都可以.如果您正在设计更大,可扩展且可能允许由非程序员创建卡片的东西(无论是实际需要还是您正在进行研究),那么这可能不会.
假设后者,让我们看一些替代方案.
您可以将卡的适当责任放在卡上,而不是一些外部代码.例如play(Context& c),向卡添加功能(Context作为访问板的手段和可能需要的任何东西).板卡会知道它可能只适用于板子而且不需要演员.
但我完全放弃使用继承.它的许多问题之一是它如何引入所有卡的分类.让我给你举个例子:
BoardCard并ActionCard把所有的卡在这两个桶;Action是Board卡片还是卡片;BoardActionCard类型或任何不同的方式);RedBoardCard,BlueBoardCard,RedActionCard等?应该避免继承的原因以及如何实现运行时多态性的其他示例,否则您可能希望观看Sean Parent的优秀"继承是邪恶的基类"谈话.实现这种多态性的有前途的库是dyno,但我还没有尝试过.
可能的解决方案可能是:
class Card final {
public:
template <class T>
Card(T model) :
model_(std::make_shared<Model<T>>(std::move(model)))
{}
void play(Context& c) const {
model_->play(c);
}
// ... any other functions that can be performed on a card
private:
class Context {
public:
virtual ~Context() = default;
virtual void play(Context& c) const = 0;
};
template <class T>
class Model : public Context {
public:
void play(Context& c) const override {
play(model_, c);
// or
model_.play(c);
// depending on what contract you want to have with implementers
}
private:
T model_;
};
std::shared_ptr<const Context> model_;
};
Run Code Online (Sandbox Code Playgroud)
然后,您可以为每种卡类型创建类:
class Goblin final {
void play(Context& c) const {
// apply effects of card, e.g. take c.board() and put the card there
}
};
Run Code Online (Sandbox Code Playgroud)
或者实现不同类别的行为,例如有一个
template <class T>
void play(const T& card, Context& c);
Run Code Online (Sandbox Code Playgroud)
模板然后使用enable_if来处理不同的类别:
template <class T, class = std::enable_if<IsBoardCard_v<T>>
void play(const T& card, Context& c) {
c.board().add(Card(card));
}
Run Code Online (Sandbox Code Playgroud)
哪里:
template <class T>
struct IsBoardCard {
static constexpr auto value = T::IS_BOARD_CARD;
};
template <class T>
using IsBoardCard_v = IsBoardCard<T>::value;
Run Code Online (Sandbox Code Playgroud)
然后定义你Goblin的:
class Goblin final {
public:
static constexpr auto IS_BOARD_CARD = true;
static constexpr auto COLOR = Color::RED;
static constexpr auto SUPERMAGIC = true;
};
Run Code Online (Sandbox Code Playgroud)
这将允许您在许多方面对您的卡进行分类,也可以通过实现不同的play功能完全专门化行为.
示例代码使用std :: shared_ptr来存储模型,但您绝对可以在这里做更聪明的事情.我喜欢使用静态大小的存储空间,只允许使用特定最大尺寸和对齐的Ts.或者,您可以使用std :: unique_ptr(虽然会禁用复制)或利用小尺寸优化的变体.
小智 0
我哪里设计的不好?
Card问题是,每当引入新类型时,您总是需要扩展该代码。
我怎样才能避免dynamic_cast?
避免这种情况的通常方法是使用接口(即纯抽象类):
struct ICard {
virtual bool can_put_on_board() = 0;
virtual ~ICard() {}
};
class BoardCard : public ICard {
public:
bool can_put_on_board() { return true; };
};
class ActionCard : public ICard {
public:
bool can_put_on_board() { return false; };
};
Run Code Online (Sandbox Code Playgroud)
这样,您可以简单地使用引用或指针来ICard检查它所保存的实际类型是否可以放在Board.
但我找不到任何具有适当用法的文章(展示良好的设计,而不仅仅是“如何使用”)。
总的来说,我想说动态转换没有任何好的、现实生活中的用例。
有时我在 CRTP 实现的调试代码中使用它,例如
template<typename Derived>
class Base {
public:
void foo() {
#ifndef _DEBUG
static_cast<Derived&>(*this).doBar();
#else
// may throw in debug mode if something is wrong with Derived
// not properly implementing the CRTP
dynamic_cast<Derived&>(*this).doBar();
#endif
}
};
Run Code Online (Sandbox Code Playgroud)