【问题标题】:C++ - Good or bad practice?C++ - 好还是坏的做法?
【发布时间】:2014-02-20 09:57:12
【问题描述】:

我正面临这样的情况,我发现将对象的类型(作为枚举)存储在基类中以进一步将指向该基类的指针转换为子类指针,这取决于它的值。输入。

例如:

class CToken
{
public:
   token_type_e eType;
};

class COperatorToken : public CToken
{
public:
    // Sub class specific stuff
};

class CLiteralToken : public CToken
{
public:
    // Sub class specific stuff
};

然后

vector<CToken *> aTokens;

//...

for( size_t nI = 0, nMaxI = aTokens.size(); nI < nMaxI; ++nI )
{
   switch( aTokens[ nI ]->eType )
   {
   case E_OPERATOR :
      // Do something with sub-class specific stuff.
      break;
   case E_LITERAL :
      // Do something with sub-class specific stuff.
      break;
   }
}

这是一种不好的做法吗?

谢谢你:)

编辑:

假设我正在分析我的令牌列表。在某些时候,我会想检查当前令牌是否是使用虚拟函数virtual bool isOperator() 的运算符,正如人们所建议的那样。 现在,如果它是一个运算符,我将想要访问该子类特定的东西以找出例如它是哪种类型的运算符。在这种情况下,我该怎么办?我无法在我的基类中添加方法 getOperatorType(),这没有任何意义。除了转换为子类来检索该子类成员值之外,还有其他方法吗?

【问题讨论】:

  • 它通常表明你有一个有缺陷的设计,如果你需要沮丧的话。但是,C++ 有一个内置的方法可以让你做到这一点,使用 dynamic_cast
  • 您是否有可用的运行时类型信息 (RTTI)?如果是这样,为什么不使用多态机制。

标签: c++ object casting polymorphism introspection


【解决方案1】:

类型字段确实违背了 C++ 面向对象的特性。通常你可以用 polymorphism 解决这个问题:

CToken 中定义一个函数virtual doSomething(),带有适当的参数和返回类型。

COperatorTokenCLiteralToken 中实现该功能。

如果您随后使用aTokens[ nI ]-&gt;doSomething();,运行时将调用相应的函数

【讨论】:

  • 感谢您的回答!我已经编辑了我的问题,请问您怎么看? :)
【解决方案2】:

听起来您在尝试模拟代数数据类型。通常,您不会这样做,而是通过在基类上放置一个纯 virtual 函数并在派生类的覆盖中实现实际行为。

此外,如果您确实需要您建议的模式,语言运行时会知道类型,因此您无需存储它:

#include <iostream>
#include <typeinfo>

class Token { };

class Operator : public Token { };

class Literal : public Token { };

int main()
{
    Token *tok = new Literal();

    if (typeid(*tok) == typeid(Literal)) {
        std::cout << "got a literal\n";
    }
    else if (typeid(*tok) == typeid(Operator)) {
        std::cout << "got an operator\n";
    }
}

【讨论】:

  • 感谢您的回答。我不知道 typeid() 是什么,但我不想使用 C++11 功能(如果这是 C++11)。
  • @Virus721 typeid 至少从 C++98 开始就存在了。见here
【解决方案3】:

我会问一个不同的问题:您为什么要重新发明轮子而不是使用现有的可能性,即虚拟成员(多态性)?因此,如果没有充分的理由这样做,我会称之为不好的做法。

您可以重载虚拟成员,即使您的指针属于基类,您仍然会调用实际(子)类的成员(请注意,您通常也需要一个虚拟析构函数,但我为简单起见,我跳过这个):

class Token {
public:
    virtual void somethingSpecial() {
        std::cout << "Hello!" << std::endl;
    }
}

class Literal : public Token {
public:
    virtual void somethingSpecial() {
        std::cout << "I'm a literal!" << std::endl;
    }
}

class Operator : public Token {
public:
    virtual void somethingSpecial() {
        std::cout << "I'm an operator!" << std::endl;
    }
}

然后,在你的迭代中,你可以做一些简单的事情:

std::vector<Token*> tokens;

tokens.push_back(new Literal());
tokens.push_back(new Operator());
tokens.push_back(new Literal());

for (std::vector<Token*>:iterator a = tokens.begin(); a != tokens.end(); ++a)
    a->somethingSpecial();

结果将与您的代码相似。实际运行的代码将基于子类中的实际实现。在这种情况下,您最终会得到以下输出:

我是一个字面意思!

我是操作员!

我是一个字面意思!

如果子类没有实现虚函数,则会调用基类的版本(除非您将其抽象化;否则它将是必须具备的)。

【讨论】:

  • 感谢您的回答!我已经编辑了我的问题,请问您怎么看? :)
  • 你为什么想知道你有哪个运营商?整个想法的一部分是“外部世界”不必知道任何关于实施的事情。因此,您可能只需添加一个函数process(),然后查看堆栈中的元素或获取实际要使用的文字。
【解决方案4】:

你可以这样做:

class CToken {
  public:
    virtual bool isLiteral(void) const = 0;
//...
};

并在两个子类上定义它。

然后,如果您想使用运算符或文字这一事实,解决方案是声明 getValue() 函数,但永远不要为此使用 switch(...)。事实上,如果你想考虑面向对象的实践,你应该在CToken 中创建一个虚函数,它允许子类自动解释自己,运算符作为运算符,值作为值。

【讨论】:

  • 虽然这是真的,但为什么还要打扰这样一个虚拟成员而不是让实际调用成为一个虚拟成员?
  • 感谢您的回答!我已经编辑了我的问题,请问您怎么看? :)
  • 因为它需要两个子类来实现这个功能。然后,主要对象 CToken 是一个接口,所以任何使用 CToken 类型对象的东西都可以询问它是否是文字。
【解决方案5】:

你不应该从不向下投 - 除非你需要。

一个合适的简单例子是在框架中传递消息。假设每种消息类型都必须是可序列化的,因此基类接口将提供一个虚拟的“serialise()”方法,该方法被派生类覆盖,并且任何类型的所有消息都由消息传递框架进行多态处理。

但是,这些消息可能代表什么,因此它们所拥有的属性和行为可能大相径庭 - 从 GPS 坐标到电子邮件再到星历数据,以及试图捕获基类接口中的所有多样性的情况都会导致没有什么意义。一旦代理收到一条消息,它就知道它感兴趣(根据消息类型),然后(并且只有这样)向下转换为实际正确的类型以访问有意义的消息内容是合适的 - 因为你已经达到了不能以纯粹抽象的方式处理消息。

向下转型本身并不是失败。使用 RTTI 的成本远高于添加一个简单的枚举字段,并且经常推荐的“保持动态强制转换直到某些东西起作用”应该受到比我想象中更残酷的惩罚!

【讨论】:

  • 实际上 CToken 类存在的唯一原因是允许我将所有这些不同类型的令牌存储到一个数组中,然后我可以分析。如果我遇到一个操作符标记,我想知道它的类型,如果我遇到一个文字标记,我想知道该文字的值。我解析这些标记列表以从中创建树(表示表达式),其中运算符作为节点,文字/变量作为叶子。我需要访问子类数据来创建这些树节点和叶子。
  • 我的示例并不是你的示例,只是一个示例。 ;-) 但是同样的论点也适用,如果您的标记需要针对某些情况进行抽象,但在它们的具体情况下可能具有非常不同的属性和行为,那么依此类推。
猜你喜欢
  • 2016-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-29
  • 2021-07-06
  • 2020-05-19
  • 1970-01-01
  • 2013-01-24
相关资源
最近更新 更多