投给一个孩子

Cast to a Child

本文关键字:一个 孩子      更新时间:2023-10-16

我实际上想做的是在这个问题中转换一个构造好的moneypunctpunct_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();
};

我可以用默认构造函数构造ParentChild,但这不会设置Complex members。所以说我得到了Parent foo,它是使用自定义构造函数构造的,我想用Childfunc方法来使用foo对象。我怎么做呢?直接的dynamic_cast<Child*>(&foo)分段错误,所以可能没有办法:http://ideone.com/JcAaxd

auto bar = dynamic_cast<Child*>(&foo);

我是否必须使Child构造函数接受Parent并在内部复制它?或者是否有某种方式将bar投射到存在中?

编辑:

为了深入了解我的实际问题,示例中的Parentmoneypunct,它在标准中实现,因此我无法修改它。

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;
}

注意以下几点:

  1. 友谊是不可继承的,所以你必须在ParentHelper中列出你打算使用的所有子节点。
  2. 但是,它确实给了你一种方法来访问你的父类的所有数据成员,它不会导致一些奇怪的行为。
  3. 这可能仍然不是你想要的,但从你的问题,我认为它可能会有所帮助。

这是特定于实现的正如维基百科所说:

c++标准没有明确规定动态分派必须如何实现,但是编译器通常在相同的基本模型上使用微小的变化。

通常,编译器为每个类创建一个单独的虚函数表。当一个对象被创建时,指向该虚表的指针(称为虚表指针、vpointer或VPTR)作为该对象的隐藏成员被添加。编译器还在每个类的构造函数中生成"隐藏"代码,将该类对象的虚指针初始化为相应虚表的地址。

更糟的是Danny Kalev说:

编译器可以根据其vtr的位置分为两类。UNIX编译器通常将vptr放在最后一个用户声明的数据成员之后,而Windows编译器将它作为对象的第一个数据成员,在任何用户声明的数据成员之前。

我给出了前面的所有信息来指示我的hack将工作的条件:

  1. is_standard_layout失败,因为它有:"有虚拟函数或虚拟基类"
  2. 你的父对象没有复制构造函数或赋值操作符(否则你可以在子对象的构造中复制父对象)
  3. 你的编译器实际上实现了一个v表
  4. 你的v表是在对象布局的开始还是结束
  5. 在对象布局中,编译器分配给子成员变量相对于父成员变量的位置

了解了您正在进入的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()无论如何都会被调用。

如果父节点不是子节点,而你想要子节点。函数被调用,那么你的设计就坏了。

将基类对象的地址赋值给派生类的指针是不合法的。

如果你想在派生类中调用虚函数,你必须实例化派生类的对象,并通过该对象或指向该对象的指针(其类型可能是基类)来调用它。

基类和派生类中的虚函数表是分开的,所以不能通过基类的对象访问派生类的虚函数