Let's say we have a concrete class Apple
. (Apple objects can be instantiated.) Now, someone comes and derives an abstract class Peach
from Apple. It's abstract because it introduces a new pure virtual function. The user of Peach is now forced to derive from it and define this new function. Is this a common pattern? Is this correct to do?
假设我们有一个具体的Apple类。 (Apple对象可以被实例化。)现在,有人来自Apple并从中派生出一个抽象类Peach。它是抽象的,因为它引入了一个新的纯虚函数。 Peach的用户现在*从中派生出来并定义这个新功能。这是一种常见的模式吗?这是正确的吗?
Sample:
Now let's say we have a concreteclass Apple { public: virtual void MakePie(); // more stuff here };
class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };
class Peach:public Apple {public:virtual void MakeDeliciousDesserts()= 0; //这里有更多的东西};
class Berry
. Someone derives an abstract
class Tomato
from Berry. It's abstract because it overwrites one of Berry's virtual functions, and makes it pure virtual. The user of Tomato has to re-implement the function previously defined in Berry. Is this a common pattern? Is this correct to do?
Sample:
Note: The names are contrived and do not reflect any actual code (hopefully). No fruits have been harmed in the writing of this question.class Berry { public: virtual void EatYummyPie(); // more stuff here };
class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };
class Tomato:public Berry {public:virtual void EatYummyPie()= 0; //这里有更多的东西};
7 个解决方案
#1
8
Re Peach from Apple:
来自Apple的Re Peach:
- Don't do it if Apple is a value class (i.e. has copy ctor, non-identical instances can be equal, etc). See Meyers More Effective C++ Item 33 for why.
- Don't do it if Apple has a public nonvirtual destructor, otherwise you invite undefined behaviour when your users delete an Apple through a pointer to Peach.
- Otherwise, you're probably safe, because you haven't violated Liskov substitutability. A Peach IS-A Apple.
- If you own the Apple code, prefer to factor out a common abstract base class (Fruit perhaps) and derive Apple and Peach from it.
如果Apple是一个值类(即具有复制ctor,不相同的实例可以相等,等等),请不要这样做。有关原因,请参阅Meyers更有效的C ++项目33。
如果Apple具有公共非虚拟析构函数,请不要这样做,否则当用户通过指向Peach的指针删除Apple时,会引发未定义的行为。
否则,你可能是安全的,因为你没有违反Liskov的可替代性。桃子IS-A Apple。
如果您拥有Apple代码,则更愿意分解出一个共同的抽象基类(也许是Fruit)并从中派生出Apple和Peach。
Re Tomato from Berry:
来自Berry的Re Tomato:
- Same as above, plus:
- Avoid, because it's unusual
- If you must, document what derived classes of Tomato must do in order not to violate Liskov substitutability. The function you are overriding in Berry - let's call it
Juice()
- imposes certain requirements and makes certain promises. Derived classes' implementations ofJuice()
must require no more and promise no less. Then a DerivedTomato IS-A Berry and code which only knows about Berry is safe.
与上述相同,加上:
避免,因为它是不寻常的
如果必须,请记录番茄必须做的派生类,以免违反Liskov替代性。你在Berry中重写的功能 - 我们称之为Juice() - 强加某些要求并作出某些承诺。派生类的Juice()实现必须不再需要,并且不能再承诺。然后DerivedTomato IS-A Berry和只知道Berry的代码是安全的。
Possibly, you will meet the last requirement by documenting that DerivedTomatoes must call Berry::Juice()
. If so, consider using Template Method instead:
可能,您将通过记录DerivedTomatoes必须调用Berry :: Juice()来满足最后一个要求。如果是这样,请考虑使用模板方法:
class Tomato : public Berry
{
public:
void Juice()
{
PrepareJuice();
Berry::Juice();
}
virtual void PrepareJuice() = 0;
};
Now there is an excellent chance that a Tomato IS-A Berry, contrary to botanical expectations. (The exception is if derived classes' implementations of PrepareJuice
impose extra preconditions beyond those imposed by Berry::Juice
).
现在,番茄IS-A浆果很有可能与植物学期望相反。 (例外情况是,派生类的PrepareJuice实现强加了Berry :: Juice强加的额外前提条件)。
#2
5
It would seem to me like an indication of a bad design. Could be forced if you wanted to take a concrete definition from a closed library and extend it and branch a bunch of stuff off it, but at that point I'd be seriously considering the guideline regarding Encapsulation over Inheritance.. If you possibly can encapsulate, you probably should.
在我看来,这似乎是一个糟糕的设计的迹象。如果你想从一个封闭的库中获取一个具体的定义并扩展它并从中分出一堆东西,可能会被强制,但那时我会认真考虑关于Encapsulation over Inheritance的指导原则。如果你可能可以封装,你可能应该。
Yeah, the more I think about it, this is a Very Bad Idea.
是的,我想的越多,这是一个非常糟糕的主意。
#3
2
Not necessarily wrong, but definitely smelly. Especially if you leave the fruit out in the sun for too long. (And I don't think my dentist would like me eating concrete apples.)
不一定错,但绝对臭。特别是如果你将水果放在阳光下太久。 (而且我认为我的牙医不会喜欢吃混凝土苹果。)
Though, the main thing I see here that's smelly isn't so much the abstract class derived from a concrete class, but the REALLY DEEP inheritance hierarchy.
虽然,我在这里看到的主要内容是臭的并不是从具体类派生的抽象类,而是REALLY DEEP继承层次结构。
EDIT: re-reading I see that these are two hierarchies. All the fruit stuff got me mixed up.
编辑:重新阅读我看到这是两个层次结构。所有的水果都让我混淆了。
#4
2
If you use the recommended practice of having inheritance model "is-a" then this pattern would pretty much never come up.
如果你使用推荐的继承模型“is-a”的做法,那么这种模式几乎不会出现。
Once you have a concrete class, you are saying that it is something that you can actually create an instance of. If you then derive an abstract class from it, then something that is an attribute of the base class is not true of the derived class, which should set of klaxons that something's not right.
一旦你有一个具体的类,你就说你可以实际创建一个实例。如果你从那里派生出一个抽象类,那么作为基类属性的东西就不是派生类的真实情况,派生类应该设置一些不正确的汽车。
Looking at your example, a peach is not an apple, so it should not be derived from it. Same is true for Tomato deriving from Berry.
看看你的例子,桃子不是苹果,所以它不应该来自它。来自Berry的番茄也是如此。
This is where I would usually advise containment, but that doesn't even seem to be a good model, since an Apple does not contain a Peach.
这是我通常建议收容的地方,但这似乎不是一个好模型,因为Apple不包含Peach。
In this case, I would factor out the common interface -- PieFilling or DessertItem.
在这种情况下,我会考虑公共接口--PieFilling或DessertItem。
#5
1
a bit unusual, but if you had some other subclass of the base class and the subclasses of the abstract class had enough common stuff to justify the existance of the abstract class like:
有点不寻常,但如果你有基类的其他子类,抽象类的子类有足够的常见东西来证明抽象类的存在,如:
class Concrete
{
public:
virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
virtual void eat()=0;
// and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
void eat() {}
};
class Sub2:public Abstract {
void eat() {}
};
int main() {
Concrete *sub1=new Sub1(),*sub2=new Sub2();
sub1->eat();
sub2->eat();
return 0;
}
#6
0
Hmmm...by thinking "what a....." for a couple of seconds, I come to a conclusion it is not common... Also, I would not derive Peach from Apple and Tomato from Berry...do you have any better example?:)
嗯...通过思考“什么是.....”几秒钟,我得出结论它并不常见...而且,我不会从Berry派生出Apple和Tomato的Peach ......你有更好的例子吗?:)
It's a lot of weird shit you can do in C++...I can't even think of 1% of it...
你可以在C ++中做很多奇怪的事情......我甚至不能想到1%的...
About override a virtual with a pure virtual, you can probably just hide it and it will be really weird...
关于用纯虚拟覆盖虚拟,你可能只是隐藏它,这将是非常奇怪的...
If you can find a stupid C++ compiler that would link this function as a virtual, then you will get runtime pure virtual function call...
如果你能找到一个将这个函数作为虚拟链接的愚蠢的C ++编译器,那么你将获得运行时纯虚函数调用...
I think this can only be done for a hack and I have no idea what kind of hack really...
我认为这只能用于黑客攻击而且我不知道究竟是什么样的黑客...
#7
0
To answer your first question, you can do this since users of Apple, if given a concrete instance derived from Peach will not know any different. And the instance will not know its not an Apple (unless there are some virtual functions from Apple that are overridden that you didn't tell us about).
要回答你的第一个问题,你可以这样做,因为Apple的用户,如果给出一个源自Peach的具体实例,将不会有任何不同。实例不会知道它不是Apple(除非有一些来自Apple的虚拟函数被覆盖,你没有告诉我们)。
I can't yet imagine how useful it would be to override a virtual function with a pure virtual one - is that even legal?
我无法想象用纯虚拟功能覆盖虚拟功能会有多么有用 - 甚至是合法的吗?
In general you want to conform with Scott Meyers "Make all non-leaf classes abstract" item from his books.
一般来说,你想要与他的书中的Scott Meyers“Make all non-leaf classes abstract”项目保持一致。
Anyway, apart from that what you describe seems to be legal - its just that I can't see you needing it that often.
无论如何,除了你所描述的似乎是合法的 - 只是我不能经常看到你需要它。
#1
8
Re Peach from Apple:
来自Apple的Re Peach:
- Don't do it if Apple is a value class (i.e. has copy ctor, non-identical instances can be equal, etc). See Meyers More Effective C++ Item 33 for why.
- Don't do it if Apple has a public nonvirtual destructor, otherwise you invite undefined behaviour when your users delete an Apple through a pointer to Peach.
- Otherwise, you're probably safe, because you haven't violated Liskov substitutability. A Peach IS-A Apple.
- If you own the Apple code, prefer to factor out a common abstract base class (Fruit perhaps) and derive Apple and Peach from it.
如果Apple是一个值类(即具有复制ctor,不相同的实例可以相等,等等),请不要这样做。有关原因,请参阅Meyers更有效的C ++项目33。
如果Apple具有公共非虚拟析构函数,请不要这样做,否则当用户通过指向Peach的指针删除Apple时,会引发未定义的行为。
否则,你可能是安全的,因为你没有违反Liskov的可替代性。桃子IS-A Apple。
如果您拥有Apple代码,则更愿意分解出一个共同的抽象基类(也许是Fruit)并从中派生出Apple和Peach。
Re Tomato from Berry:
来自Berry的Re Tomato:
- Same as above, plus:
- Avoid, because it's unusual
- If you must, document what derived classes of Tomato must do in order not to violate Liskov substitutability. The function you are overriding in Berry - let's call it
Juice()
- imposes certain requirements and makes certain promises. Derived classes' implementations ofJuice()
must require no more and promise no less. Then a DerivedTomato IS-A Berry and code which only knows about Berry is safe.
与上述相同,加上:
避免,因为它是不寻常的
如果必须,请记录番茄必须做的派生类,以免违反Liskov替代性。你在Berry中重写的功能 - 我们称之为Juice() - 强加某些要求并作出某些承诺。派生类的Juice()实现必须不再需要,并且不能再承诺。然后DerivedTomato IS-A Berry和只知道Berry的代码是安全的。
Possibly, you will meet the last requirement by documenting that DerivedTomatoes must call Berry::Juice()
. If so, consider using Template Method instead:
可能,您将通过记录DerivedTomatoes必须调用Berry :: Juice()来满足最后一个要求。如果是这样,请考虑使用模板方法:
class Tomato : public Berry
{
public:
void Juice()
{
PrepareJuice();
Berry::Juice();
}
virtual void PrepareJuice() = 0;
};
Now there is an excellent chance that a Tomato IS-A Berry, contrary to botanical expectations. (The exception is if derived classes' implementations of PrepareJuice
impose extra preconditions beyond those imposed by Berry::Juice
).
现在,番茄IS-A浆果很有可能与植物学期望相反。 (例外情况是,派生类的PrepareJuice实现强加了Berry :: Juice强加的额外前提条件)。
#2
5
It would seem to me like an indication of a bad design. Could be forced if you wanted to take a concrete definition from a closed library and extend it and branch a bunch of stuff off it, but at that point I'd be seriously considering the guideline regarding Encapsulation over Inheritance.. If you possibly can encapsulate, you probably should.
在我看来,这似乎是一个糟糕的设计的迹象。如果你想从一个封闭的库中获取一个具体的定义并扩展它并从中分出一堆东西,可能会被强制,但那时我会认真考虑关于Encapsulation over Inheritance的指导原则。如果你可能可以封装,你可能应该。
Yeah, the more I think about it, this is a Very Bad Idea.
是的,我想的越多,这是一个非常糟糕的主意。
#3
2
Not necessarily wrong, but definitely smelly. Especially if you leave the fruit out in the sun for too long. (And I don't think my dentist would like me eating concrete apples.)
不一定错,但绝对臭。特别是如果你将水果放在阳光下太久。 (而且我认为我的牙医不会喜欢吃混凝土苹果。)
Though, the main thing I see here that's smelly isn't so much the abstract class derived from a concrete class, but the REALLY DEEP inheritance hierarchy.
虽然,我在这里看到的主要内容是臭的并不是从具体类派生的抽象类,而是REALLY DEEP继承层次结构。
EDIT: re-reading I see that these are two hierarchies. All the fruit stuff got me mixed up.
编辑:重新阅读我看到这是两个层次结构。所有的水果都让我混淆了。
#4
2
If you use the recommended practice of having inheritance model "is-a" then this pattern would pretty much never come up.
如果你使用推荐的继承模型“is-a”的做法,那么这种模式几乎不会出现。
Once you have a concrete class, you are saying that it is something that you can actually create an instance of. If you then derive an abstract class from it, then something that is an attribute of the base class is not true of the derived class, which should set of klaxons that something's not right.
一旦你有一个具体的类,你就说你可以实际创建一个实例。如果你从那里派生出一个抽象类,那么作为基类属性的东西就不是派生类的真实情况,派生类应该设置一些不正确的汽车。
Looking at your example, a peach is not an apple, so it should not be derived from it. Same is true for Tomato deriving from Berry.
看看你的例子,桃子不是苹果,所以它不应该来自它。来自Berry的番茄也是如此。
This is where I would usually advise containment, but that doesn't even seem to be a good model, since an Apple does not contain a Peach.
这是我通常建议收容的地方,但这似乎不是一个好模型,因为Apple不包含Peach。
In this case, I would factor out the common interface -- PieFilling or DessertItem.
在这种情况下,我会考虑公共接口--PieFilling或DessertItem。
#5
1
a bit unusual, but if you had some other subclass of the base class and the subclasses of the abstract class had enough common stuff to justify the existance of the abstract class like:
有点不寻常,但如果你有基类的其他子类,抽象类的子类有足够的常见东西来证明抽象类的存在,如:
class Concrete
{
public:
virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
virtual void eat()=0;
// and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
void eat() {}
};
class Sub2:public Abstract {
void eat() {}
};
int main() {
Concrete *sub1=new Sub1(),*sub2=new Sub2();
sub1->eat();
sub2->eat();
return 0;
}
#6
0
Hmmm...by thinking "what a....." for a couple of seconds, I come to a conclusion it is not common... Also, I would not derive Peach from Apple and Tomato from Berry...do you have any better example?:)
嗯...通过思考“什么是.....”几秒钟,我得出结论它并不常见...而且,我不会从Berry派生出Apple和Tomato的Peach ......你有更好的例子吗?:)
It's a lot of weird shit you can do in C++...I can't even think of 1% of it...
你可以在C ++中做很多奇怪的事情......我甚至不能想到1%的...
About override a virtual with a pure virtual, you can probably just hide it and it will be really weird...
关于用纯虚拟覆盖虚拟,你可能只是隐藏它,这将是非常奇怪的...
If you can find a stupid C++ compiler that would link this function as a virtual, then you will get runtime pure virtual function call...
如果你能找到一个将这个函数作为虚拟链接的愚蠢的C ++编译器,那么你将获得运行时纯虚函数调用...
I think this can only be done for a hack and I have no idea what kind of hack really...
我认为这只能用于黑客攻击而且我不知道究竟是什么样的黑客...
#7
0
To answer your first question, you can do this since users of Apple, if given a concrete instance derived from Peach will not know any different. And the instance will not know its not an Apple (unless there are some virtual functions from Apple that are overridden that you didn't tell us about).
要回答你的第一个问题,你可以这样做,因为Apple的用户,如果给出一个源自Peach的具体实例,将不会有任何不同。实例不会知道它不是Apple(除非有一些来自Apple的虚拟函数被覆盖,你没有告诉我们)。
I can't yet imagine how useful it would be to override a virtual function with a pure virtual one - is that even legal?
我无法想象用纯虚拟功能覆盖虚拟功能会有多么有用 - 甚至是合法的吗?
In general you want to conform with Scott Meyers "Make all non-leaf classes abstract" item from his books.
一般来说,你想要与他的书中的Scott Meyers“Make all non-leaf classes abstract”项目保持一致。
Anyway, apart from that what you describe seems to be legal - its just that I can't see you needing it that often.
无论如何,除了你所描述的似乎是合法的 - 只是我不能经常看到你需要它。