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

C++;使用多态数据成员的最佳实践模式 在C++中使用多态数据成员的最佳实践模式是什么? 我所知道的最简单的方法就是使用普通指针,c++,oop,C++,Oop,例如,如果数据成员的类型不是多态的: 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() { re

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

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;
}
以多种方式使用这些类很容易,错误的可能性最小

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

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;
}
有没有更好的办法?我尽量保持简单,不使用任何new/delete语句。(注意:必须能够有默认值)

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

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

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

这样做的目的是让人具有不同的个性,并通过子类化使添加更多个性变得容易。实现这一点的最佳方式是什么?因为从逻辑上讲,每种人格类型只需要一个客体,所以一个人复制或拥有人格客体并没有多大意义。有什么不同的方法/设计不需要我们处理复制/所有权问题吗?

没有新的操作符,没有一种干净简单的方法可以做到这一点,因为不同的对象可能会占用不同的内存空间。因此,食物本身不能包含水果,只能包含指向水果的指针,而水果又必须在堆上分配。因此,这里有一些替代方案

看看图书馆。它的存在正是为了解决你的问题。缺点是它增加了很多Boost依赖项,虽然它可能会使代码看起来很简单,但对于不熟悉该库的人来说,这将更难理解。当然,在下面,它仍然执行new/delete操作

要手动执行这些操作,您可以让食物接受指向水果的指针,并通过在其析构函数
~Food()
中调用delete来获得它的所有权(或者它可以使用诸如shared_ptr、scoped_ptr或auto_ptr之类的智能指针来自动删除它)。您必须清楚地记录传入的值必须用new分配。(默认值会带来特别的麻烦;您必须检查它,或者添加一个标志,或者使用
操作符new
分配该值的副本。)


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

在这种情况下,最好使用单独的默认构造函数和复制构造函数。另外,你不应该使用通过引用传入的某物的地址;它可能是堆栈上不久将消失的对象。就您的职能而言:

void SomeFunction() {
  Apple a;
  food1.SetFruit(a);
}
a
(因此
&a
也会在函数返回时变为无效,因此
food1
将包含无效指针

因为您存储的是指针,所以您应该期望
食物
拥有传入的
水果

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;
};
另一个选项是
食品
复制传递的
水果
。这很棘手。在过去,我们会定义一个
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
的存在。

您的
SetFruit()
f
的地址分配给
mu fruit
。由于
f
只是一个函数参数,因此它只在
SetFruit()
返回之前存在于堆栈上。此后,
f
消失,其地址不再有效。因此,
mu 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;
};

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

我意识到我基本上只是用Mike DeSimone的解决方案(同时发布)来描述。这表明这确实是解决这个问题的常用方法。SetFruit(Fruit&f),f只是对传递的对象的引用,所以我认为&f将给出传递的任何对象的地址。你确定吗?@Leiv如果你对此持怀疑态度,请随意选择&f和&a。我只是用一个简单的测试尝试了一下,它给了我相同的地址。通过引用传递对象不会像通过值传递那样生成本地副本。在机器代码级别,通过引用传递对象和通过值传递指针是一样的,因此Objective-C不使用引用,只使用指针。指针和引用之间的主要语义差异是指针可以为NULL(即
0
可以隐式转换为任何指针类型),引用不能为NULL(因为它只能从现有对象创建),指针变量可以更改,而引用不能。是的,我知道您可以制作通过显式类型转换“空引用”(例如
Fruit&nullFruit=*(Fruit*)0);
),但您可以通过显式类型转换颠覆许多其他规则,并破坏您的程序(例如
食品;水果*fruitInFood=*(Fruit**)&Food;
,它可能“看起来不错”,但无法保证工作).+1写得很好,很清晰。您可以使用co variant返回类型Lenence让派生对象的克隆函数返回指向派生类型的指针……只需保留多一点编译器的类型知识,更好地记录正在发生的事情,有时还可以避免强制转换。传递传入的
的所有权的弱点是指我们无法分配