Jim*_*Jim 1 c++ polymorphism programming-languages idiomatic
C++是一种面向价值的语言,似乎不支持OO(因此也就是子类型多态).对于参数多态,缺乏类型参数的类型推断和模板的详细语法使得它们难以使用.
请注意,我所熟知的唯一语言是Java(子类型多态)和Haskell(参数多态).两种语言都倾向于一种多态性.然而,C++支持(在某种程度上),但两者似乎都在我认为不直观的问题上工作.因此,当使用C++进行编程时,我很难确定应该采用何种方式编写代码.
所以我的问题是在C++中哪种多态被认为更惯用?
编辑1:
解释我的"C++不支持OO"点:
动态方法调度和LSP在OO中很常见,不是吗?但是当谈到C++时,不使用指针(原始或智能)来应用这些技术是不可能的(或实际的).
例如,考虑一个Person
带有virtual
方法的类,该方法print
将其名称输出到控制台.让另一个课程Student
延伸Person
并覆盖,print
以打印他的名字加上他学校的名字.
现在考虑以下功能:
void blah(const Person & p) {
p.print();
}
Run Code Online (Sandbox Code Playgroud)
在这里,如果我传递一个Student
对象,print
方法将调用print
从Person
,不从Student
.因此,它违背了亚型多态性的基本思想.
现在我知道在这种情况下我可以使用动态分配(即指针)来实现子类型多态.但是静态分配在C++中更常见.指针用作最后的手段(我记得在这里读过其他一些帖子).所以我觉得很难调和推荐静态分配而不是动态分配的好实践(这就是我说C++是值时的意思具有亚型多态性的.)
当使用Java时,我倾向于全面使用动态分配,因此子类型多态性在那里很自然.但是,C++并非如此,
希望我的观点现在清楚了.
编辑2:
好吧,我在编辑1中给出的例子是错误的.但我的观点仍然有效,我已多次面对这个问题.我无法回忆起我头脑中的所有这些案例.
这是我想到的一个案例.
在Java中,您可以在类中引用超类型,然后使它们指向任何子类型的实例.
例如,
class A {
B y1;
B y2;
}
abstract class B {
// yada yada
}
class B1 exyends B {
// yada yada
}
class B2 extends B {
// yada yada
}
Run Code Online (Sandbox Code Playgroud)
这里引用y1
并y2
在A
可制成指向的任何实例B1
,B2
或任何其他子类B
.无法重新分配C++引用.所以我将不得不在这里使用指针.因此,这证明在C++中,不使用指针就不可能实现各种子类型多态.
Jer*_*fin 12
添加第五张投票重新打开后,我有机会成为第一个添加另一个回复的人.让我们从C++不支持OO的说法开始.给出的例子是:
现在考虑以下功能:
void blah(const Person & p) {
p.print();
}
Run Code Online (Sandbox Code Playgroud)
如果我传递一个Student对象,print方法将从Person调用print,而不是Student.因此,它违背了亚型多态性的基本思想.
长话短说,这个例子是完全错误的 - 或者更确切地说,关于这个例子的说法是错误的.如果你传递一个Student
对象给这个函数,你会调用会Student::print
,不会 Person::print
如上述要求.因此,C++完全按照OP显然希望实现多态.
这个不是惯用C++的唯一部分就是你通常operator<<
用来打印对象,所以不应该print
(显然)打印std::cout
,而应该让它接受一个参数,而不是blah
重载operator<<
,如:
std::ostream &operator<<(std::ostream &os, Person const &p) {
return p.print(os);
}
Run Code Online (Sandbox Code Playgroud)
现在,它是可以创建一个blah
将所描述的行动,但这样做你就必须有它的价值需要它的参数:
void blah(Person p) {
p.print();
}
Run Code Online (Sandbox Code Playgroud)
因此,有一些原来的要求一定的道理-具体而言,当/如果你想使用多态,你就需要使用指针或引用.
但请注意,这与分配对象的方式无关(仅限于外围).无论相关对象如何分配,您都可以通过引用传递.如果函数接受指针,则可以传递自动或静态分配的对象的地址.如果需要引用,则可以传递动态分配的对象.
就类型推断而言,C++将其用于函数模板,而不是类模板.C++ 0x增加了decltype
一个新的含义auto
(它是一个保留字,但实际上从未在C的黎明时使用过),允许对更广泛的情况进行类型推断.它还增加了lambda表达式(缺乏这真的是与当前的C++的一个严重问题),可同时使用auto
.仍然存在不支持类型推断的情况,但是会很好 - 但至少IMO auto
(特别是)会减少相当多.
就详细程度而言,毫无疑问,它至少部分是正确的.有点像Java,你在编写C++时的舒适程度往往至少在一定程度上取决于包含各种"技巧"(例如代码完成)的编辑器,以帮助减少你输入的数量.Haskell在这方面表现出色--Haskell让你在每个字符类型上完成的工作比几乎任何其他语言都要多(APL是为数不多的明显例外之一).与此同时,值得注意的是"仿制药"(Java或C#)大约为详细,但很多比C++模板多才多艺少.就冗长而言,C++位于Haskell之间(或接近)一个极端,而Java和C#位于(或者,再次,接近)相反的极端.
找到更经常使用的原始问题:有一段时间C++没有模板,所以基本上你唯一的选择是子类型.正如你可能猜到的那样,当时它被大量使用,即使它不是真正的最佳选择.
C++已经有很长一段时间的模板了.模板现在非常普遍,它们基本上是不可避免的.例如,最初仅使用继承的IOStreams现在也使用模板.标准容器,迭代器和算法都大量使用模板(完全避开继承).
因此,较旧的代码(以及来自较旧或更保守的编码员的新代码)倾向于主要或完全专注于子类型.更新和/或更自由编写的代码往往更多地使用模板.至少根据我的经验,最合理的最近代码使用两者的混合.在这两者之间,我通常会在必要时使用子类型,但在他们可以完成工作时更喜欢模板.
编辑:演示代码显示多态性:
#include <iostream>
class Person {
public:
virtual void print() const { std::cout << "Person::print()\n"; }
};
class Student : public Person {
public:
virtual void print() const { std::cout << "Student::print()\n"; }
};
void blah(const Person &p) {
p.print();
}
int main() {
Student s;
blah(s);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
结果(剪切并粘贴在我的计算机上面运行的代码,使用MS VC++编译):
Student::print()
Run Code Online (Sandbox Code Playgroud)
所以,是的,它完全按照你的需要进行多态,并注意在这个例子中,有问题的对象是在堆栈上分配的,而不是使用new
.
编辑2 :(响应编辑问题):
确实,您无法分配参考.这与多态性的问题是正交的 - 例如,无论你想要分配的内容与初始化的内容是相同还是不同,你都不能以任何方式进行赋值.
至少对我来说,这似乎很明显,必须有一定的参考和指针之间的差异的能力,要么就已经没有理由将引用添加到语言.如果要将它们分配给不同的对象,则需要使用指针而不是引用.一般来说,我会尽可能使用引用,如果必须,我会使用指针.至少IMO,作为类成员的引用通常是高度可疑的(例如,它意味着您不能分配该类型的对象).下图:如果你想要引用的内容,一定要使用引用 - 但抱怨因为引用不是指针似乎(至少对我而言)没有多大意义.