投给一个孩子

时间:2015-07-14 03:09:49

标签: c++ inheritance casting polymorphism virtual-functions

我实际上要做的是将构造的moneypunct投射到http://slick.typesafe.com/doc/3.0.0/api/index.html#scala.slick.package中的punct_facet,而不像this question那样编写复制构造函数。

但是为了写this answer,我们说我有这两个类:

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,我想使用foo Child方法func对象。我怎么做?直截了当dynamic_cast<Child*>(&foo)段错误,所以可能没有办法:Minimal, Complete, Verifiable Example

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

我是否必须创建一个Child构造函数,该构造函数需要Parent并在内部复制它?或者是否有某种方法可以使bar成为现实?

修改

为了深入了解我的实际问题,示例中的Parentmoneypunct,这是在标准中实现的,因此我无法对其进行修改。

class punct_facet是我的,而Child来自示例,它继承了moneypunct,如果我试图保持与实施无关,我甚至无法在内部使用moneypunct &#39;成员变量。

这意味着我必须数据镜像moneypunct中的所有punct_facet成员变量,并在punct_facet构造上复制它们。这导致一个对象的胖度是它需要的两倍,并要求我重新实现所有moneypunct功能。

显然这是不受欢迎的,但我能找到的唯一方法就是采用先前构建的moneypunct并按照此问题的要求将其视为punct_facet

4 个答案:

答案 0 :(得分:2)

它不会像您想象的那样工作,因为您已将函数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;
}

虽然我可能会收到一些发布这个破损代码的downvotes,但我认为有必要展示在这种情况下发生的事情。您需要将指针转换为对象,然后指定您要调用的函数。

根据您的工作情况,最好使用朋友类来完成:

#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. 这可能仍然不是你想要的,但从你的问题我觉得它可能会有所帮助。

答案 1 :(得分:1)

所以这是特定于[{3}}的实现:

  

C ++标准并未明确规定必须如何实施动态调度,但编译器通常会在同一基本模型上使用较小的变体。
  
  通常,编译器为每个类创建单独的vtable。创建对象时,会将指向此vtable的指针(称为虚拟表指针,vpointer或VPTR)添加为此对象的隐藏成员。编译器还会生成&#34; hidden&#34;每个类的构造函数中的代码,用于将其对象的vpointers初始化为相应vtable的地址。

更糟糕Wikipedia notes

  

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

我提供了上述所有信息,以表明我的黑客工作的条件:

  1. Danny Kalev states对您的孩子失败,因为它有:&#34;有虚拟功能或虚拟基类&#34;
  2. 您的父母有一个复制构造函数或赋值运算符(否则您只能在构造子对象时复制父级)
  3. 您的编译器实际上实现了一个v-table
  4. 您的v-table是否位于对象布局的开头或结尾
  5. 编译器相对于对象布局中的父成员变量分配给子成员变量的位置
  6. 通过了解您正在进入的黑客攻击,我现在将继续扩展问题中的课程,以更好地展示我们如何能够“投射到孩子身上”:

    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

    is_standard_layout 虽然在这里没有要求提供如何实现多重继承:Live example

    这是moneypunct的一个有用的解决方案,因为它的初始化将是特定于实现的,因为C ++没有指定除以下语言之外的任何语言环境名称:

    • ""
    • "C"
    • "POSIX"

    我想结束这个回答,指出这一整篇文章都是关于如何克服moneypunct的设计者有意设置的限制。所以,是的,你可以这样做,但是当你这样做时,应该问一个明显的问题:&#34;为什么moneypunct的复制构造函数和赋值运算符首先被删除了?我故意颠覆这个设计的哪个方面?&#34;

答案 2 :(得分:0)

如果是父ISA,那么无论如何都会调用Child.func()。

如果父级不是Child,并且您希望调用Child.func,那么您的设计就会被破坏。

答案 3 :(得分:0)

将基础对象的地址分配给派生类的指针是不合法的。

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

基类和派生类中的虚函数表是分开的,因此您无法通过基类对象访问派生类的虚函数