在没有动态转换或静态向下转换的情况下比较C ++中的派生类

时间:2012-01-26 16:54:00

标签: c++ oop

我正在尝试将公共基类的对象进行比较。当两个对象在类中不同或者对象的特定值不同时,比较应该失败(例如,输出失败字符串)。理想情况下,比较以某种方式强制执行,这样新的派生类也必须将比较函数写入其类的成员。这是一个代码示例:

#include <iostream>
#include <string>
#include <vector>

class Vehicle 
{ 
public:
  virtual std::string compareTo(Vehicle* v) = 0;
};

class Bicycle : public Vehicle
{
public:
  Bicycle() { color_ = "red"; }
  std::string compareTo(Vehicle* v) { return "We're different vehicles."; }
  std::string compareTo(Bicycle* b) { return color_.compare(b->color_) ? "We're different bicycles." : "We're the same bicycle."; }

private:
  std::string color_;
};

class Car : public Vehicle
{
public:
  Car() { style_ = "sedan"; }
  std::string compareTo(Vehicle* v) { return "We're different vehicles."; }
  std::string compareTo(Car* c) { return style_.compare(c->style_) ? "We're different cars." : "We're the same car."; }

private:
  std::string style_;
};

int main()
{
  Vehicle* compareFrom = new Bicycle();

  std::vector<Vehicle*> compareTos;
  compareTos.push_back(new Bicycle());
  compareTos.push_back(new Car());

  std::vector<Vehicle*>::iterator it;
  for (it = compareTos.begin(); it != compareTos.end(); ++it)
    std::cout << compareFrom->compareTo(*it) << std::endl;

  return 0;
}

目前,输出(你可以看到here)说“我们是不同的车辆”。我知道这种情况正在发生,因为我正在使用抽象基指针。问题是如何解决它!

喜欢的输出是自行车输出它们是相同的,因为它们具有相同的颜色。自行车和汽车应该输出它们是不同的车辆。不同颜色的自行车和不同风格的汽车也应该输出它们的不同。我觉得必须有一个伟大的模式来解决这个问题,但我陷入了动态投射或不安全的垂头问题。另外,我希望在同一个班级的成员之间强制执行比较功能(所以自行车必须能够与其他自行车比较)。

4 个答案:

答案 0 :(得分:11)

你想要 Multiple Dispatch (即根据多个变量选择动态调用哪个函数,而不仅仅是'this')。这是因为你需要以某种方式检查类型,否则编译器将对类型进行静态分析并选择要调用的函数(Vehicle中的虚拟函数)。

没办法解决这个问题。 dynamic_cast是您的朋友,但出于性能(或其他)原因,您可能想要推出自己的RTTI系统。 (维基百科文章显示了一种方式..)

 std::string Bicycle::compareTo(Vehicle* v) { 
    if (Bicycle* b = dynamic_cast<Bicycle*>(v)) {
       return compareTo(b);
    } else {
       return "We're different vehicles."; 
    }
 }

Loki C++ library中存在此模式的实现,如果您有许多需要比较的类型,这可能会有所帮助。

C ++中的语言不支持多个分派,大多数主流语言也不支持。有人建议将其添加到C ++ 11中,请参阅此questionBjarne's paper。我认为它被拒绝是因为(已知和未知)动态链接问题,C ++标准遗憾地对此一无所知。

答案 1 :(得分:6)

您的代码存在一个很大的问题,即它不易扩展(违反open/closed principle)。但是,您可以将比较委托给基类方法。

另外,如果你想强制执行语义(一件好事)那么你将无法绕过预测,抱歉。

为了使其健壮且可扩展,

  1. 使基本方法成为纯虚拟
  2. 为基本方法提供实现(是的,这有效!即使它是纯虚拟的),也可以比较对象的类型
  3. 在派生类中,使用基类的实现来测试类型相等,然后进行实际的逻辑检查。
  4. #include <iostream>
    #include <iomanip>
    #include <string>
    #include <typeinfo>
    
    struct vehicle {
        virtual bool compare_to(vehicle const& other) const = 0;
    };
    
    bool vehicle::compare_to(vehicle const& other) const {
        return typeid(*this) == typeid(other);
    }
    
    struct car : vehicle {
        std::string color;
    
        car(std::string const& color) : color(color) { }
    
        bool compare_to(vehicle const& other) const {
            bool result = vehicle::compare_to(other);
            return result and (color == static_cast<car const&>(other).color);
        }
    };
    
    struct bike : vehicle {
        int spokes;
    
        bike(int spokes) : spokes(spokes) { }
    
        bool compare_to(vehicle const& other) const {
            bool result = vehicle::compare_to(other);
            return result and (spokes == static_cast<bike const&>(other).spokes);
        }
    };
    
    int main() {
        car c1("blue");
        car c2("red");
        bike b1(42);
    
        std::cout << std::boolalpha;
        std::cout << c1.compare_to(c2) << "\n"
                  << c1.compare_to(b1) << "\n"
                  << c1.compare_to(c1) << "\n";
    }
    

    上面的代码,static_cast是安全的,因为我们事先确保了类型是相同的,因此演员阵容永远不会失败。

    请注意,在此使用typeid完全合法。它甚至不应该是非常低效的,因为没有深度类型的层次结构来行走。但是如果你想提高效率,你可以实现一个简单的自己的机制,它使用基类中的静态表将每个创建的实例映射到类型唯一的数字标识符(例如std::map<vehicle*, type_id>,其中type_id是一个普通的旧enum)并执行简单的查找。

    ......或者实际使用dynamic_cast

答案 2 :(得分:4)

我通常使用基类中的“kind”成员来实现它。我发现这有一些优点:

  • 性能 - 无需虚拟函数调用和动态强制转换
  • 通过为每个类类型使用不同的“位”,可以进行更高级别的比较。例如,“独轮车”和“自行车”可能都是人力驱动的,因此您可以轻松地检查它与主要类型。

种类如下:

    enum Kind {
      HUMAN_POWERED  = (0x1 << 0),
      MOTOR_POWERED  = (0x1 << 1),
      BICYCLE        = (0x1 << 2) | HUMAN_POWERED,
      UNICYCLE       = (0x1 << 3) | HUMAN_POWERED,
      CAR            = (0x1 << 4) | MOTOR_POWERED
    };

现在可以检查CAR是不是自行车,但是如果有两种类型是MOTOR_POWER,也可以!

bool areSameClass (Vehicle const & lhs, Vehicle const & rhs)
{
  return (lhs->getKind () & rhs->getKind ()) & (HUMAN_POWERED | MOTOR_POWERED);
}

答案 3 :(得分:0)

如果在编译器中启用了RTTI,则可以使用typeid()运算符,但这将要求您的类具有多态性。