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&amp;,C&amp;) 不会与somefunction(B&amp;,B&amp;) 冲突。演员所做的就是消除需要使用哪个功能的歧义。 但我必须做一些检查,看看它是 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&amp;, A&amp;);。为什么会调用somefunction(B&amp;, B&amp;);

【讨论】:

【参考方案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++函数重载问题的主要内容,如果未能解决你的问题,请参考以下文章

C++的函数重载

C++复制构造函数和=号重载问题

函数重载与函数模板 - C++

C++深度剖析学习总结 22 类中的函数重载

C++深度剖析学习总结 22 类中的函数重载

什么是C++重载?