【问题标题】:c++ problem with function overloadingc++函数重载问题
【发布时间】: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:好吧,保持原样,使用类型转换,因为我想要的东西是不可能的。感谢大家的帮助。

【问题讨论】:

    标签: c++ polymorphism overloading


    【解决方案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 维。感谢大家帮助我!

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-07-13
              • 1970-01-01
              • 2012-12-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多