投给一个孩子
Cast to a Child
我实际上想做的是在这个问题中转换一个构造好的moneypunct
到punct_facet
,而不像这个答案中那样编写复制构造函数。
但是为了写出一个最小的、完整的、可验证的例子,假设我有这两个类:
class Parent{
public:
Parent(Complex& args);
Parent operator=(const Parent&) = delete;
Parent(const Parent&) = delete;
Parent() = default;
virtual void func();
private:
Complex members;
};
class Child : public Parent{
public:
virtual void func();
};
我可以用默认构造函数构造Parent
或Child
,但这不会设置Complex members
。所以说我得到了Parent foo
,它是使用自定义构造函数构造的,我想用Child
的func
方法来使用foo
对象。我怎么做呢?直接的dynamic_cast<Child*>(&foo)
分段错误,所以可能没有办法:http://ideone.com/JcAaxd
auto bar = dynamic_cast<Child*>(&foo);
我是否必须使Child
构造函数接受Parent
并在内部复制它?或者是否有某种方式将bar
投射到存在中?
为了深入了解我的实际问题,示例中的Parent
是moneypunct
,它在标准中实现,因此我无法修改它。
class punct_facet
是我的,Child
从例子中,它继承了moneypunct
,如果我试图保持实现独立,我甚至不能内部使用moneypunct
的成员变量。
这意味着我必须在punct_facet
中对所有moneypunct
成员变量进行数据镜像,并在punct_facet
构造上复制它们。这导致一个对象比它需要的大两倍,并且需要我重新实现所有的moneypunct
功能。
显然,这是不希望的,但我能找到的唯一方法是采取以前构建的moneypunct
,并将其视为punct_facet
根据这个问题的要求。
它不会像你想的那样工作,因为你已经使函数func
虚拟。这意味着,即使将指向Parent
的指针转换为指向Child
的指针,该对象的func()
仍然是Parent::func()
。
现在,理论上你可以这样做:
#include <iostream>
class Parent
{
public:
virtual void foo() { std::cout << "parent" << std::endl; }
};
class Child : public Parent
{
public:
virtual void foo() { std::cout << "child" << std::endl; }
};
int main()
{
Child child;
child.foo(); // "child"
child.Parent::foo(); // "parent"
Parent parent;
parent.foo(); // "parent"
((Child*)(&parent))->foo(); // still "parent"
((Child*)(&parent))->Child::foo(); // "child"
return 0;
}
虽然我可能会收到一些反对张贴这个破碎的代码,我认为这是必要的,以显示在这种情况下发生了什么。您需要转换两者,即指向对象的指针,然后精确指定您打算调用哪个函数。
根据你正在做的事情,最好使用友类来完成:
#include <iostream>
class ParentHelper;
class ChildHelper;
class Parent
{
friend class ParentHelper;
friend class ChildHelper;
private:
int a=5;
};
class ParentHelper
{
public:
virtual void func(Parent *p)
{
std::cout << "parent helper, but i see a " << p->a << std::endl;
}
};
class ChildHelper : public ParentHelper
{
public:
virtual void func(Parent *p)
{
std::cout << "child helper, but i see a also " << p->a << std::endl;
}
};
void foo(Parent* p, ParentHelper *h)
{
h->func(p);
}
int main()
{
Parent p;
ParentHelper ph;
ChildHelper ch;
ph.func(&p);
ch.func(&p);
foo(&p, &ph);
foo(&p, &ch);
return 0;
}
注意以下几点:
- 友谊是不可继承的,所以你必须在ParentHelper中列出你打算使用的所有子节点。
- 但是,它确实给了你一种方法来访问你的父类的所有数据成员,它不会导致一些奇怪的行为。
- 这可能仍然不是你想要的,但从你的问题,我认为它可能会有所帮助。
这是特定于实现的正如维基百科所说:
c++标准没有明确规定动态分派必须如何实现,但是编译器通常在相同的基本模型上使用微小的变化。
通常,编译器为每个类创建一个单独的虚函数表。当一个对象被创建时,指向该虚表的指针(称为虚表指针、vpointer或VPTR)作为该对象的隐藏成员被添加。编译器还在每个类的构造函数中生成"隐藏"代码,将该类对象的虚指针初始化为相应虚表的地址。
更糟的是Danny Kalev说:
编译器可以根据其vtr的位置分为两类。UNIX编译器通常将vptr放在最后一个用户声明的数据成员之后,而Windows编译器将它作为对象的第一个数据成员,在任何用户声明的数据成员之前。
我给出了前面的所有信息来指示我的hack将工作的条件:
-
is_standard_layout
失败,因为它有:"有虚拟函数或虚拟基类" - 你的父对象没有复制构造函数或赋值操作符(否则你可以在子对象的构造中复制父对象)
- 你的编译器实际上实现了一个v表
- 你的v表是在对象布局的开始还是结束
- 在对象布局中,编译器分配给子成员变量相对于父成员变量的位置
了解了您正在进入的hack之后,我现在将继续扩展问题中的类,以更好地演示如何"Cast to a Child":
class Parent{
public:
Parent operator=(const Parent&) = delete;
Parent(const Parent&) = delete;
Parent() = default;
Parent(int complex) : test(complex) {}
virtual void func(){ cout << test; }
private:
int test = 0;
};
class Child : public Parent{
public:
Child operator=(const Child&) = delete;
Child(const Child&) = delete;
Child() = default;
Child(const Parent* parent){
const auto vTablePtrSize = sizeof(void*);
memcpy(reinterpret_cast<char*>(this) + vTablePtrSize,
reinterpret_cast<const char*>(parent) + vTablePtrSize,
sizeof(Parent) - vTablePtrSize);
}
virtual void func(){ cout << "Child, parent says: "; Parent::func(); }
};
我们只是使用memcpy
来复制父对象的状态,同时允许所有Child
信息保持。
你可以看到这段代码:
Parent foo(13);
Child bar(&foo);
bar.func();
将打印:
孩子,家长说:13
活的例子,虽然问题中没有要求,但这里是如何实现多重继承的:http://ideone.com/1QOrMz
对于moneypunct
来说,这是一个有用的解决方案,因为它的初始化将是特定于实现的,因为c++没有指定任何区域名称,除了:
-
""
-
"C"
-
"POSIX"
我想通过指出整篇文章都是关于如何克服moneypunct
设计师故意设置的限制来结束这个答案。所以,是的,您可以这样做,但是当您这样做时,应该问一个明显的问题:"为什么首先删除moneypunct
的复制构造函数和赋值操作符?"我有意颠覆了设计的哪个方面?
如果父类是Child类,那么Child.func()无论如何都会被调用。
如果父节点不是子节点,而你想要子节点。函数被调用,那么你的设计就坏了。
将基类对象的地址赋值给派生类的指针是不合法的。
如果你想在派生类中调用虚函数,你必须实例化派生类的对象,并通过该对象或指向该对象的指针(其类型可能是基类)来调用它。
基类和派生类中的虚函数表是分开的,所以不能通过基类的对象访问派生类的虚函数
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 运行同一解决方案的另一个项目的项目
- 挂起和取消挂起一个文件DLL
- 用C++中的一个变量定义一个常量
- 函数向量_指针有不同的原型,我可以构建一个吗
- 在c++中用vector填充一个简单的动态数组
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- 预处理器:插入结构名称中的前一个行号
- 我在c++代码中生成了一个运行时#3异常
- 我可以制作一个基于孩子具有不同回报的虚函数吗?
- 添加一个孩子(引擎统一)
- 我怎样才能得到一个QObject的孩子?
- 从二进制搜索树中删除只有一个孩子的节点
- 开始一个过程,而不是作为一个孩子
- MPI,通过其中一个过程生成一个孩子
- 继承.如何做一个孩子=父母
- 投给一个孩子
- 孩子在等待另一个孩子