函数重载的c ++问题

时间:2010-01-29 02:24:21

标签: c++ polymorphism overloading

我遇到了函数重载的问题。我将向您展示一些简单的例子:

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: 好吧保持原样,使用类型转换,因为我想要的东西是不可能的。谢谢大家的帮助。

10 个答案:

答案 0 :(得分:9)

静态抛出它们,以便编译器知道要选择哪一个:

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");
}

答案 1 :(得分:3)

要调用的函数仅在运行时针对虚拟方法确定,具体取决于对象的类型:

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&amp;实际上将是B&amp;并且不希望强制转换,我得出结论,在编译时类型将是硬编码,因此您可以尝试“编译时多态”。 (在这种情况下,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,就选择过载而言。 (我添加了一些consts,因为否则会产生与非const引用绑定的东西更难。)

答案 2 :(得分:2)

正如其他人已经提到的那样,编译器会选择正确的重载 - 它是如何运行的。

如果您确定实例的类型,那么您应该进行投射。如果没有,可以在运行时绕过手动类型检查的一种方法是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);

答案 3 :(得分:1)

你究竟想做什么?看起来你正在尝试编写一个给定两个对象的函数,并且你希望它根据对象组合的类型做一个不同的事情?

请记住,即使是正常的多态也会在内部进行“检查”。

这是一个有趣的问题,

polymorphism使您能够根据ONE对象的类型轻松地重载函数的功能,而不是两个。

您正在尝试做什么?我最好的建议是让每个对象分别执行自己的特定内容,然后返回一个共同的对象进行常规处理:

class Base
{
  virtual SomeComonInterfaceObject DoMySpecialSomething() = 0;
}

void _doSomething(SomeComonInterfaceObject a, SomeComonInterfaceObject b);

void doSomething(Base& o1, Base& o2)
{ 
     _doSomething(o1->DoMySpecialSomething(), o2->DoMySpecialSomething());
}

如果不合适,您可能只需检查类型并根据该类型执行具体操作。

请注意,即使是普通的多态也会“检查”你是否担心性能,其他语言也必须这样做。

你能够解决的唯一方法是使用模板,它可能会变得非常丑陋。

知道你想要做什么会很有趣。另外,这些doSomething函数,总是这样,它们的两个参数是同一类型吗?或者他们混合搭配?

答案 4 :(得分:0)

是的,但是C ++决定在编译时使用哪个函数,而不是在运行时。在编译时,编译器唯一看到的是(A&amp;,A&amp;) - 它无法知道那些实际上是B的实例。

答案 5 :(得分:0)

你应该发布更多代码....什么是
A和a1 = ...
A和a2 = ......

你不应该使用指针吗? 如果你将a1和a2存储为A类,那么即使它们也是B,也会调用A重载。你必须动态播放它们。

答案 6 :(得分:0)

在这种情况下,编译器将始终调用somefunction(A&, A&);。为什么会拨打somefunction(B&, B&);

答案 7 :(得分:0)

你在评论中说你确定他们是B的。

如果是这种情况,那么这就是你想要做的。

B a1(); B a2();

如果你需要A,你可以这样做(A *)和B。这是一个隐式演员,我很确定它会在编译时发生。

答案 8 :(得分:0)

您的编译器选择了它认为最合适的重载。 a1和a2都被声明为对A类的引用,因此它们适合重载,它引用A类“更好”而不是它们适合另一个,因为这需要某种隐式转换来将它们转换为B类。 / p>

另请注意,您不能以这种方式隐式转播。如果您有一个指针或对基类实例的引用(在本例中为A),则它不能隐式转换为派生类,因为通常并非基类的所有实例都是派生类的实例(所有的B都是As,但不是所有的B都是。)

在调用函数之前,您需要将它们声明为B的实例:

B& b1 = ...
B& b2 = ...
somefunction(b1, b2);

答案 9 :(得分:0)

我要做的是使用调度表来获得我想要的东西。它可以是2维或3维(可能是2维),而不是1维。谢谢所有人试图帮助我!