c++函数重载问题
Posted
技术标签:
【中文标题】c++函数重载问题【英文标题】:c++ problem with function overloading 【发布时间】:2011-01-10 16:43:57 【问题描述】:我遇到了函数重载的问题。我将向您展示一些简单的示例:
class A ;
class B : public A;
void somefunction(A&, A&);
void somefunction(B&, B&);
void someotherfunction()
...
A& a1 = ...
A& a2 = ...
...
a1 和 a2 都是 B 但的实例
somefunction(a1,a2);
通话
void somefunction(A&, A&);
我做错了什么?我的意思是多态性和重载是为了这样的东西,不是吗?
编辑:好的,现在我知道它不起作用(感谢您的回答)。
任何解决方案如何做到这一点?没有强制转换。
edit2:好吧,保持原样,使用类型转换,因为我想要的东西是不可能的。感谢大家的帮助。
【问题讨论】:
【参考方案1】:静态地转换它们,以便编译器知道选择哪一个:
void somefunction((B&)a1, (B&)a2);
您遇到此问题的原因是程序设计,而不是语言。编译器根据传入的类型选择使用哪个函数。C# 的行为方式完全相同(很确定 Java 也会如此)。
在我看来,您在错误的地方实现了多态性。 somefunction
确实属于 a
类,应该是虚拟的。然后,无论何时在运行时在 a
的实例上调用它时,都会调用正确类中的覆盖。
所以,真的应该是这样的:
class a
public:
virtual somefunction(a& a2)
//do stuff
class b : public a
virtual somefunction(a& a2)
b& b2 = (b&)a2;
//do stuff
class c : public b
virtual somefunction(a& a2)
c& c2 = (c&)a2;
//do stuff
上述解决方案在虚函数内部使用最少的强制转换,并假设两个实例的类型相同。这意味着b.somefunction(a())
将具有未定义的行为。
更好的解决方案是依赖 C++ RTTI 并使用 dynamic_cast,如果无法进行向下转换,它将返回 NULL。
这个问题被称为double dispatch problem,在***文章中的描述与你描述的差不多。此外,***为multiple dispatch 提供的唯一解决方案是使用dynamic_cast
。
编辑好的,这一直困扰着我,这是一个基类和两个子类之间完全双重调度的解决方案。它并不漂亮,并且使用了一些 C++ 技巧,例如朋友类(实际上是为了更好地封装,而不是相反)和前向声明。
class b;
class c;
class a
protected:
virtual void somefunction(a& a2); //do stuff here
virtual void somefunction(b& b2); //delegate to b
virtual void somefunction(c& c2); //delegate to c
public:
virtual void doFunc(a& a2)
a2.somefunction(*this);
friend class b;
friend class c;
;
class b : public a
protected:
virtual void somefunction(a& a2); //do stuff here
virtual void somefunction(b& b2); //do stuff here
virtual void somefunction(c& c2); //delegate to c
public:
virtual void doFunc(a& a2)
a2.somefunction(*this);
friend class a;
;
class c : public b
protected:
virtual void somefunction(a& a2); //do stuff here
virtual void somefunction(b& b2); //do stuff here
virtual void somefunction(c& c2); //delegate to c
public:
virtual void doFunc(a& a2)
a2.somefunction(*this);
friend class a;
friend class b;
;
//class a
void a::somefunction(a& a2)
printf("Doing a<->a");
void a::somefunction(b& b2)
b2.somefunction(*this);
void a::somefunction(c& c2)
c2.somefunction(*this);
//class b
void b::somefunction(a& a2)
printf("Doing b<->a");
void b::somefunction(b& b2)
printf("Doing b<->b");
void b::somefunction(c& c2)
c2.somefunction(*this);
//class c
void c::somefunction(a& a2)
printf("Doing c<->a");
void c::somefunction(b& b2)
printf("Doing c<->b");
void c::somefunction(c& c2)
printf("Doing c<->c");
【讨论】:
正如我所说,我想避免类型转换。如果我有另一个类 C isa A 和另一个 somefunction(C&,C&) 怎么办? (我实际上有,我只是想发布一个简单的例子)somefunction(C&,C&)
不会与somefunction(B&,B&)
冲突。演员所做的就是消除需要使用哪个功能的歧义。
但我必须做一些检查,看看它是 B 型还是 C 型才能调用该函数。我想避免任何检查或类型转换。
那你应该使用更动态的语言而不是c++
哇,你在那里使用的技巧很好 :) 我在这里发帖(下面有几篇文章),我使用了另一种解决方案(调度表),但因为我真的很喜欢这篇文章,它回答了我的问题我认为这是我问题的最终答案。非常感谢!【参考方案2】:
仅在虚拟方法的运行时根据 this 对象的类型确定要调用的函数:
A* a = new B;
a->foo(); //calls B::foo (as long as foo is virtual)
在运行时不会根据函数参数的“真实”类型解析要调用的函数。
A* a = new B;
X* x = new Y;
a->foo(x); //assuming virtual and two overloads, calls B::foo(X*), not B::foo(Y*)
没有内置的双重调度机制(根据两个对象的动态类型同时选择要调用的函数),尽管可以手动实现该模式,如一些帖子所示。
如果您说您总是知道 A& 实际上是 B& 并且不想要强制转换,我的结论是类型将是硬编码已知的编译时,因此您可以尝试“编译时多态性”。 (在这种情况下,A 和 B 甚至不需要关联,只要它们有合适的接口即可。)
class A ;
class B ;
class C: public A ;
void somefunction(const A&, const A&);
void somefunction(const B&, const B&);
template <class T>
void someotherfunction()
const T& a1 = T();
const T& a2 = T();
somefunction(a1, a2);
int main()
someotherfunction<A>();
someotherfunction<B>();
//combine with inheritance and it will still be
//possible to call somefunction(A&, A&) since
//somefunction(C&, C&) is not defined
someotherfunction<C>();
现在 a1 和 a2 将真正在一个实例中为 As,而在另一种情况下为 Bs,就选择重载而言。 (我添加了一些 const,否则会更难生成绑定到非 const 引用的东西。)
【讨论】:
否,类型不会在编译时进行硬编码。但是感谢您尝试帮助我! 我的意思不是严格的硬编码,而是在编译时使用模板生成的。如果类型真的只是在运行时确定,我看不出你怎么能如此自信地说你的演员表总是正确的。 - 不过,你知道得更好,因为这可能意味着非常大的架构变化。请记住,继承并不是解决所有类型问题的灵丹妙药(我相信,像您这样的问题是 STL 没有使用运行时多态性实现的部分原因)。【参考方案3】:正如其他人已经提到的,编译器会选择正确的重载 - 它是语言的工作方式。
如果你真的确定实例是什么类型,你就应该强制转换。如果没有,您可以在运行时绕过手动类型检查的一种方法是double dispatch:
struct A;
struct B;
struct Base
virtual perform(Base& b) = 0;
virtual perform(A& a) = 0;
virtual perform(B& b) = 0;
;
struct A : Base
virtual perform(Base& b) b.perform(*this);
virtual perform(A& a) someFunction(a, *this);
virtual perform(B& b) someFunction(b, *this);
;
struct B : A
virtual perform(Base& b) b.perform(*this);
virtual perform(A& a) someFunction(a, *this);
virtual perform(B& b) someFunction(b, *this);
;
// ...
Base& b1 = foo1();
Base& b2 = foo2();
b1.perform(b2);
【讨论】:
【参考方案4】:你到底想做什么?看起来您正在尝试编写一个函数来执行给定两个对象的操作,并且您希望它根据对象组合的类型执行不同的操作?
请记住,即使是正常的多态性也会在内部进行“检查”。
这是一个有趣的问题,
多态性使您能够轻松地根据一个对象的类型重载函数的功能,而不是两个。
您到底想做什么?我最好的建议是让每个对象都可以分别执行自己的特定操作,然后返回一个通用对象进行通用处理:
class Base
virtual SomeComonInterfaceObject DoMySpecialSomething() = 0;
void _doSomething(SomeComonInterfaceObject a, SomeComonInterfaceObject b);
void doSomething(Base& o1, Base& o2)
_doSomething(o1->DoMySpecialSomething(), o2->DoMySpecialSomething());
如果这不适合,您可能只需要检查类型并根据它进行具体操作。
请注意,如果您担心性能,即使是正常的多态性也会“检查”,任何其他语言也必须这样做。
您可能能够解决这个问题的唯一方法是使用模板,它可能会变得非常丑陋。
知道你想做什么会很有趣。还有,这些doSomething函数,它们的两个参数是否总是相同的类型?还是混搭?
【讨论】:
就是这样,但正如你所说,这不适合我:(不,参数并不总是相同的类型,它们可能会像你说的那样混合和匹配。我想做的是做一些仅对派生类的某些组合进行特定操作,并以某种方式在最通用的函数(使用 &Base、&Base)中“捕获”它并进行一些错误处理。【参考方案5】:是的,但是 C++ 决定在编译时使用哪个函数,而不是在运行时。在编译时,编译器唯一看到的是 (A&, A&) - 它无法知道这些实际上是 B 的实例。
【讨论】:
好吧,但编译器不应该对这样的东西使用后期绑定吗? 'Late Binding' 用于链接代码。与本案无关。 @George:不。它可以这样做:在运行时检查参数类型,并确定调用哪个函数。但这不是语言的定义方式,所以不会。 :) 是的,它可以。我认为确实如此(对我来说看起来合乎逻辑),但我想有充分的理由不这样做。 在这种特殊情况下它可以静态完成,但在一般情况下,A& 可以是任何东西。您不希望行为依赖于准确静态分析的逻辑可能性/不可能性,对吗?或者,如果您认为,从技术上讲,它总是可以在运行时使用 RTTI 选择重载,那么抱歉,那不会是 C++(它不应该是脚本语言)。【参考方案6】:您应该发布更多代码....什么是 A& a1 = ... A& a2 = ...
你不应该使用指针吗? 如果您将 a1 和 a2 存储为 A 类型,那么即使它们也是 B 的 A 重载也会被调用。您必须对它们进行动态转换。
【讨论】:
我想避免强制转换..那里的代码不需要,它 100% 确定它们是 B 实例。【参考方案7】:在这种情况下,编译器将始终调用somefunction(A&, A&);
。为什么会调用somefunction(B&, B&);
?
【讨论】:
【参考方案8】:您在评论中说您确定他们是 B。
如果是这样,那么这就是你想要做的。
B a1(); B a2();
如果您需要 A,您可以这样做 (A*)&B。这是一个隐式转换,我很确定它发生在编译时。
【讨论】:
【参考方案9】:您的编译器选择了它认为最合适的重载。 a1 和 a2 都被声明为对 A 类的引用,因此它们适合对 A 类的引用的重载比对另一个“更好”的引用,因为这需要某种隐式转换才能将它们转换为 B 类。
另请注意,您不能以这种方式隐式向上转换。如果您有一个指向基类实例(在本例中为 A)的指针或引用,则它不能隐式转换为派生类,因为通常并非所有基类的实例都是派生类的实例(所有 B 都是 As,但并非所有 As 都是 B)。
在调用函数之前,您需要将它们声明为 B 的实例:
B& b1 = ...
B& b2 = ...
somefunction(b1, b2);
【讨论】:
【参考方案10】:我要做的是使用调度表来获得我想要的。它可能是 2 或 3 维(可能是 2 维),而不是 1 维。感谢大家帮助我!
【讨论】:
以上是关于c++函数重载问题的主要内容,如果未能解决你的问题,请参考以下文章