对于重载函数,调用父子实例的专用版本

For an overloaded function, calling specialized version for parent and child instances

我之前问过 ,但我的示例没有正确模拟我的问题。所以这是我的实际问题:

  1. 我有 class A,class B 继承自 A
  2. 我有两个函数 foo(A&)foo(B&),
  3. 我有一个 A* 指针列表,其中包含 AB 的实例。
  4. 如何为 A 的实例调用 foo(A&) 和为 B 的实例调用 foo(B&)?约束:我可以修改 AB 的实现,但不能修改 foo 的实现。

看下面的例子:

#include <iostream>
#include <list>

class A {
public:
};

class B : public A {
public:
};

void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }

int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(**it);
}

虽然我使用的是带指针的容器,但 bar 是通过父对象 class 而不是子对象 class:

调用的
# ./a.out
This is an A
This is an A
#

我期待

This is a B

将指针传递给 bar(通过重写其签名)没有帮助。

感谢安东尼奥帮助澄清问题。

您正在寻找 运行 时间多态性。虚拟成员方法支持 "naturally"。

另一种方法是使用 RTTI 和 dynamically cast A*B* 并在成功时调用 bar ...或 static_cast 如果你是确实有 B* 个对象。一般需要向下转表示设计有问题

重要说明:运行-time check in dynamic_cast 无论如何都要求类型是多态的。也许您的特定 A 可以满足此要求,但您无法更改 class。如果没有,static_cast 是唯一可用的选项。

如果您可以控制 class 您,可以使用标准多态性和重载机制,使用 this 上的虚拟方法作为 "external" 调用的外观:

#include <iostream>
#include <list>

class A;
void external_bar(A&);

class A {
public:
virtual void bar() { external_bar(*this); };
};

class B;
void external_bar(B&); //IMPORTANT
class B : public A {
public:
virtual void bar() { external_bar(*this); };
};

void external_bar(A &a) { std::cout << "This is an A" << std::endl; }
void external_bar(B &b) { std::cout << "This is a B" << std::endl; }


int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();
}

这也有缺点。需要前向声明。并且您需要注意所有内容都已正确定义,因为如果您忘记行 // IMPORTANT 编译器将为 A& 选择 external_bar 的定义,因为它是隐式可转换的,您可能会得到发现错误真是令人头疼。

编辑: 这回答了问题的第一个版本,现在改为 .


如果你这样做:

A* b = B();

那么 *b 将是类型 A。这就是您在 for 循环中所做的。这其中没有 "virtuality" 或 polimorfism。

以下代码给出了您正在寻找的行为:

class A {
public:
virtual void bar() { std::cout << "This is an A" << std::endl; }
};

class B : public A {
public:
virtual void bar() { std::cout << "This is a B" << std::endl; }
};



int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  l.push_back(new A());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();
}

以我上面的例子为例,在那种情况下:

b->bar();

将打印 This is a b.

其他人已经解释了如何实现。

我只会限制自己为什么会这样。

B 在此处隐式转换为 A。所以它目前只有A的属性。

向上转换在 C++ 中是隐式的。

仅当您的基 class 是多态的时,才能在 C++ 中进行向下转换。

简而言之,多态性要求只不过是您的基础 class 中的某些东西,可以被您的派生覆盖!! 虚拟方法

然后你可以使用 RTTI 和 dynamic_cast 按照其他人的规定来做到这一点。

示例:

#include <iostream>
#include <list>

class A {
public:
virtual void dummy() = 0;
};

class B : public A {
public:
void dummy() { }
};

void bar(A &a) { std::cout << "This is an A" <<  std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }

int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());

//Prints A
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(**it); 

//Prints B
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(dynamic_cast<B&>(**it));
}


Answer:
This is an A
This is an A
This is a B
This is a B

注意:这仅适用于您的列表包含类型 B 的对象。否则,它会崩溃。这只解释了向上转换与向下转换

Antonio 写了一个很好的涉及虚函数的解决方案。如果出于某种原因你真的不想使用虚函数,那么你可以在自由函数中使用 dynamic_cast 代替:

#include <iostream>
#include <list>

struct A {
    virtual ~A() {}   // important
};

struct B : A {};

void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }

void bar_helper(A *ptr)
{
    if ( auto b = dynamic_cast<B *>(ptr) )
        bar(*b);
    else
        bar(*ptr);
}

int main()
{
    std::list<A *> ls;
    ls.push_back(new B);
    ls.push_back(new B);
    ls.push_back(new A);

    for (auto ptr : ls)
    {
        bar_helper(ptr);
        delete ptr;
    }
    ls.clear();
}

由于重载是在编译时解决的,您需要向编译器提供足够的信息来决定要调用的 bar 的正确重载。由于您希望根据 object 的 run-time 类型动态做出该决定,因此虚拟函数会有很大帮助:

struct A {
    virtual void bar() { bar(*this); }
};

struct B : public A {
    virtual void bar() { bar(*this); }
};

看起来身体是一样的,所以B::bar可以被淘汰,但事实并非如此:虽然身体看起来完全一样,但由于C++ 中重载的静态解析:

  • A::bar 内部 *this 的类型是 A&,因此调用第一个重载。
  • B::bar里面,*this的类型是B&,所以调用了第二个重载。

修改调用代码调用成员bar将完成修改:

std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();