在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对象的所有权没有多大意义。是否有任何不同的方法/设计不需要我们处理复制/所有权问题?
答案 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;
};
多态性要求您使用指针执行所有操作(或将引用作为某些参数,但这不适用于您的情况)。