对于重载函数,调用父子实例的专用版本
For an overloaded function, calling specialized version for parent and child instances
我之前问过 ,但我的示例没有正确模拟我的问题。所以这是我的实际问题:
- 我有 class
A
,class B
继承自 A
,
- 我有两个函数
foo(A&)
和 foo(B&)
,
- 我有一个
A*
指针列表,其中包含 A
和 B
的实例。
- 如何为
A
的实例调用 foo(A&)
和为 B
的实例调用 foo(B&)
?约束:我可以修改 A
和 B
的实现,但不能修改 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();
我之前问过
- 我有 class
A
,classB
继承自A
, - 我有两个函数
foo(A&)
和foo(B&)
, - 我有一个
A*
指针列表,其中包含A
和B
的实例。 - 如何为
A
的实例调用foo(A&)
和为B
的实例调用foo(B&)
?约束:我可以修改A
和B
的实现,但不能修改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();