C ++使用多态数据成员的最佳实践模式

时间:2010-11-12 04:51:46

标签: c++ oop

在C ++中使用多态数据成员的最佳实践模式是什么? 我知道最简单的就是使用普通指针。

例如,如果数据成员的类型不是多态的:

class Fruit {
  public:
    int GetWeight() { return 1; }
};

class Food {
  public:
    Food(Fruit f=Fruit()) : m_fruit(f) {}
    void SetFruit(Fruit f) { m_fruit = f; }
    Fruit GetFruit() { return m_fruit; }
    int Test() { return m_fruit.GetWeight(); }
  private:
    Fruit m_fruit;
};
// Use the Food and Fruit classes
Food food1; //global scope
void SomeFunction() {
  Fruit f;
  food1.SetFruit(f);
}
int main() {
  Fruit fruit1, fruit2;
  Food food2(fruit1);
  food2.SetFruit(fruit2);
  food2.SetFruit(Fruit());
  SomeFunction();
  food1.Test();
  food2.Test();
  return 0;
}

很容易以多种方式使用这些类,并且错误的可能性最小。

现在,如果我们将Fruit作为基类(使用多态),这是我能想到的最简单/最好的:

class Fruit {
  public:
    virtual int GetWeight() { return 0; }
};

class Apple : public Fruit {
  public:
    virtual int GetWeight() { return 2; }
};

class Banana : public Fruit {
  public:
    virtual int GetWeight() { return 3; }
};

class Food {
  public:
    Food(Fruit& f=defaultFruit) : m_fruit(&f) {}
    void SetFruit(Fruit& f) { m_fruit = &f; }
    Fruit& GetFruit() { return *m_fruit; }
    int Test() { return m_fruit->GetWeight(); }
  private:
    Fruit* m_fruit;
    static const Fruit m_defaultFruit = Fruit();
};
// Use the Food and Fruit classes
Food food1; //global scope
void SomeFunction() {
  Apple a;
  food1.SetFruit(a);
}

int main() {
  Apple a;
  Banana b;
  Food food2(a);
  food2.SetFruit(b);
  food2.SetFruit(Apple()); //Invalid ?!
  SomeFunction(); //Weakness: the food1.m_fruit points to invalid object now.
  food1.Test();   //Can the compiler detect this error? Or, can we do something to assert m_fruit in Food::Test()?
  food2.Test();
  return 0;
}

有没有更好的方法呢?我试着保持简单,不使用任何新的/删除语句。 (注意:必须能够有默认值)

更新 让我们考虑一个更实际的例子:

class Personality {
public:
  void Action();
};
class Extrovert : public Personality {
  ..implementation..
};
class Introvert : public Personality {
  ..implementation..
};

class Person {
  ...
  void DoSomeAction() { personality->Action(); }
  ...
  Personality* m_personality;
};

目的是让具有不同个性的人,并通过子类化轻松添加更多的个性。实现这个的最佳方法是什么?从逻辑上讲,我们只需要为每种类型的Personality提供一个对象,对于Person复制或承担Personality对象的所有权没有多大意义。是否有任何不同的方法/设计不需要我们处理复制/所有权问题?

3 个答案:

答案 0 :(得分:3)

在这种情况下,您最好使用单独的默认和复制构造函数。此外,您不应该通过引用传递的内容的地址;它可能是堆栈中的一个对象很快就会消失。就你的功能而言:

void SomeFunction() {
  Apple a;
  food1.SetFruit(a);
}

a(以及&a)在函数返回时变为无效,因此food1将包含无效指针。

由于您正在存储指针,因此您应该期望Food承担传入的Fruit的所有权。

class Food {
  public:
    Food() : m_fruit(new Fruit()) {}
    Food(Fruit* f) : m_fruit(f) {}
    void SetFruit(Fruit* f) { if(m_fruit != f) { delete m_fruit; m_fruit = f; } }
    Fruit& GetFruit() { return *m_fruit; }
    const Fruit& GetFruit() const { return *m_fruit; }
    int Test() { return m_fruit->GetWeight(); }
  private:
    Fruit* m_fruit;
};

另一个选项是Food制作传递的Fruit副本。这很棘手。在过去,我们将定义一个Fruit::clone()方法,子类将覆盖:

class Fruit {
  public:
    virtual int GetWeight() { return 0; }
    virtual Fruit* clone() const { return new Fruit(*this); }
};

class Apple : public Fruit {
  public:
    virtual int GetWeight() { return 2; }
    virtual Fruit* clone() const { return new Apple(*this); }
};

class Banana : public Fruit {
  public:
    virtual int GetWeight() { return 3; }
    virtual Fruit* clone() const { return new Banana(*this); }
};

我认为你不能以你需要的方式制作虚拟构造函数。

然后Food类更改为:

class Food {
  public:
    Food() : m_fruit(new Fruit()) {}
    Food(const Fruit& f) : m_fruit(f.clone()) {}
    void SetFruit(const Fruit& f) { delete m_fruit; m_fruit = f.clone(); }
    Fruit& GetFruit() { return *m_fruit; }
    const Fruit& GetFruit() const { return *m_fruit; }
    int Test() { return m_fruit->GetWeight(); }
  private:
    Fruit* m_fruit;
};

通过此设置,SomeFunction将有效,因为food1不再依赖于a的存在。

答案 1 :(得分:1)

没有operator new,没有一种干净简单的方法,因为不同的对象可能占用内存中不同的空间。所以Food不能包含Fruit本身,而只能指向Fruit的指针,而Fruit又必须在堆上分配。所以这里有一些替代方案。

看看图书馆boost::any。它的存在正是为了解决您的问题。缺点是它增加了许多Boost依赖项,虽然它可能使你的代码看起来很简单,但对于那些不熟悉库的人来说,理解它会更难。当然,它下面还是新的/删除。

要手动执行这些操作,您可以让Food接受指向Fruit的指针,并通过在其析构函数~Food()中调用delete来获取它的所有权(或者它可以使用像shared_ptr,scoped_ptr或auto_ptr这样的智能指针,将其自动删除)。你必须清楚地记录传入的值必须用new分配。 (默认值会带来特殊的烦恼;您必须检查它,或者添加标记,或者也可以使用operator new分配该值的副本。)

您可能希望能够将任何Fruit作为参数并在堆上复制它。这需要更多的工作,因为你在制作副本时不知道它实际上是哪个Fruit。解决它的一种常见方法是向Fruit添加一个虚拟方法clone(),派生的Fruits必须适当地实现。

答案 2 :(得分:0)

您的SetFruit()正在将f的地址分配给m_fruit,而不是传入的地址。由于f只是一个函数参数,它存在于只有SetFruit()返回之前的堆栈。在此之后,f消失,其地址不再有效。因此,m_fruit指的是不存在的东西。

相反,做这样的事情:

class Food {
  public:
    Food(Fruit* f = &defaultFruit) : m_fruit(f) {}
    void SetFruit(Fruit* f) { m_fruit = f; }
    Fruit* GetFruit() { return m_fruit; }
    int Test() { return m_fruit->GetWeight(); }
  private:
    Fruit* m_fruit;
    static const Fruit m_defaultFruit;
};

多态性要求您使用指针执行所有操作(或将引用作为某些参数,但这不适用于您的情况)。