【发布时间】:2011-12-22 22:10:57
【问题描述】:
假设我有两个类继承了一个具有纯虚函数的基类。这两个类都实现了自己的函数版本,但不添加额外的成员变量,因此它们具有相同的大小。现在有时,在程序执行的中间,我想将一个类转换为另一个类,而不复制它的所有数据。所以基本上我想让它使用其他类的虚拟表。有便携的方法吗?
【问题讨论】:
假设我有两个类继承了一个具有纯虚函数的基类。这两个类都实现了自己的函数版本,但不添加额外的成员变量,因此它们具有相同的大小。现在有时,在程序执行的中间,我想将一个类转换为另一个类,而不复制它的所有数据。所以基本上我想让它使用其他类的虚拟表。有便携的方法吗?
【问题讨论】:
挪威 Andersen Consulting(现为埃森哲)的一位年轻同事曾向我提出一个严重问题。他们使用 Visual Basic 开发的应用程序需要很长时间才能加载。他怀疑这可能是因为他们将每个类都放在了自己的 DLL 中?
担心最坏的情况,我进一步询问。是的,他们也有任意崩溃等问题。
他怀疑其他莫名其妙的崩溃可能与他们通过替换 vtable 指针在运行时更改对象类型的巧妙方案有关?
我建议也许他们不应该真的做这些事情。他怀疑地看着我,并冒险说他们没有时间再从头开始做事。事实上,他们已经在扩展它,并且存在各种问题,例如他们的项目负责人坚持他们在客户现场工作而不是参加强制性会议。对我来说,这听起来像是蘑菇管理(让它们在黑暗中,当一个头弹出时,将它剪掉):这些东西经常在一起。
无论如何,我给你同样的建议:不要。
也许您可以改为实现快速 move 操作来将数据从 a 移动到 b?
或者,也许你会发现这都是过早优化的情况?
【讨论】:
如何使用新展示位置?这可能不是很便携,但它确实做了所需的事情——替换了 vtable,仅此而已。只需要照顾构造函数 - 使用一个空的。
struct Base
{
int someData;
virtual int GetValue() = 0;
};
struct A : public Base
{
int GetValue() override { return 11111; }
};
struct B : public Base
{
int GetValue() override { return 22222; }
};
A ob;
ob.someData = 123;
auto ob2 = new (&ob) B;
auto value = ob2->GetValue();
没有提及诸如班级规模、最佳实践等显而易见的事情。
【讨论】:
尽管这个问题很老,但我想提出一种方法来解决这个问题。 (不太确定可移植性)
据我了解,您有一个类 B 和 C 从某个类 A 继承,并且它们之间只存在一个虚拟函数。 (如果B 和C 也不相关,我在这里介绍的方法也有效。)
class A {
public:
virtual std::string hello() = 0;
};
class B : public A {
public:
virtual std::string hello() { return "B"; }
};
class C : public A {
public:
virtual std::string hello() { return "C"; }
};
然后您想将B 带到C,然后致电hello 并获得"B"。
因此,有一种方法可以创建 boost::any 的淡化版本,只要它适合,它就会将任何东西投射到任何东西上:)
struct parent {};
template< typename T >
struct child : public parent {
child(T const& t): item(t){}
mutable T item;
};
template< typename T >
T& as(parent const & p) { return static_cast< child< T > const& >(p).item; }
然后将它们混合在一起:
B b;
parent* p = new child< B >(b);
std::cout << as< C >(*p).hello() << std::endl;
// ==== OUTPUT ====
// B
可以看到正在运行的代码here。
为了更进一步,我们可以创建一个从一种类型转换为另一种类型的函数,而无需提供 gnat 的后端来了解它们之间发生的事情。
template< typename TO, typename FROM >
TO& convert(FROM const& from) {
parent* p = new child< FROM >(from);
return as< TO >(p);
};
这可以运行here。
(意识到我错过了这些示例代码链接中的继承,但在阅读了问题后,我认为这是真正想要的。所以,要查看没有继承的测试,请转到 here)
我开始使用的一些其他代码,我认为可能对一些人也有帮助......
#include <iostream>
#include <string>
class B {
public:
virtual char hello() {return 'B';}
};
class C {
public:
virtual int hello() {return 65;}
};
struct parent {};
template< typename T >
struct child : public parent {
child(T const& t): item(t){}
mutable T item;
};
template< typename T >
T& as(parent const & p) { return static_cast< child< T > const& >(p).item; }
template< typename TO, typename FROM >
TO& convert(FROM const& from) {
parent* p = new child< FROM >(from);
return as< TO >(*p);
};
int main()
{
B b;
std::cout << convert< C, B >(b).hello() << std::endl;
C c;
std::cout << convert< B, C >(c).hello() << std::endl;
}
// ==== OUTPUT ====
// 66
// A
想出了如何在转换函数中完成这一切:
template< typename TO, typename FROM >
TO& convert(FROM const& from) {
struct parent {};
struct child : public parent {
child(FROM const& t): item(t){}
mutable FROM item;
};
struct sibling : public parent {
sibling(TO const& t): item(t){}
mutable TO item;
};
parent* p = new child(from);
return static_cast< sibling const& >(*p).item;
};
【讨论】:
正如其他答案所说,实际上更改 vtable 绝对是不可移植的。
但是,有几种变通方法可以让您在不实际更改类类型的情况下完成类似的语义:
这个最简单的解决方案是使用描述当前实现的枚举“滚动你自己的”继承:
class MyClass
{
public:
enum DerivedType { A, B };
private:
DerivedType myType;
public:
void myVirtualFunction()
{
if (myType == A)
myAFunction();
else
myBFunction();
}
}
您还可以将函数指针用作公共成员变量,该变量设置为指示类类型的函数。然后你可以将函数指针设置为另一个类的函数来“改变它的类型”
由于您提到要避免复制数据,因此您可以保留不同的类,但具有指向所有成员变量的引用计数指针,以便您可以快速创建彼此相反类型的新对象。
【讨论】:
实现这一点的可移植方式是实现您自己的类系统,该类系统实际上具有可以复制的虚拟指针。
在标准 C++ 中没有虚拟指针之类的东西。
【讨论】:
不。就语言而言,没有虚拟表之类的东西,更不用说关于它的外观/包含什么/存储位置的规则了。
某种形式的组合可能更适合您的任务。
【讨论】:
有便携的方法吗?
绝对不是。规范没有定义虚拟函数如何实现的细节,因此没有可移植方法可以假装一个虚拟类是另一个。
【讨论】: