投射到 Child

Cast to a Child

我实际上想做的是将构造的 moneypunct 转换为 without writing a copy constructor as in 中的 punct_facet

但是为了写一个 Minimal, Complete, Verifiable Example 假设我有这两个 类:

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 object 与 Childfunc 方法一起使用。我怎么做?直接 dynamic_cast<Child*>(&foo) 段错误,所以可能没有办法:http://ideone.com/JcAaxd

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

我是否必须创建一个接受 Parent 并在内部复制它的 Child 构造函数?或者有什么方法可以将 bar 变为存在?

编辑:

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

class punct_facet 是我的,示例中的 Child 继承了 moneypunct,如果我想保持实现独立,我什至不能在内部使用 moneypunct的成员变量。

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

显然这是不可取的,但我能找到的唯一方法是采用先前构建的 moneypunct 并将其视为此问题所要求的 punct_facet

如果父 ISA 子,则无论如何都会调用 Child.func()。

如果父项不是子项并且您希望调用 Child.func 那么您的设计就失败了。

它不会像您想象的那样工作,因为您已将函数 func 设为虚拟。这意味着即使您将指向 Parent 的指针转换为指向 Child 的指针,object 的 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;
}

虽然我可能会因为发布这个损坏的代码而收到一些反对票,但我认为有必要展示在这种情况下发生的事情。您需要转换两者,即指向 object 的指针,然后准确指定您要调用的函数。

根据你在做什么,使用 friend classes:

可能会更好
#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. 友谊不会继承,因此您必须列出所有 children 到 Parent 您打算使用的助手。
  2. 但是,它确实为您提供了一种按原样访问 Parent class 的所有数据成员的方法,它不会导致一些奇怪的行为。
  3. 这可能仍然不是您要查找的内容,但根据您的问题,我认为它可能有所帮助。

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

如果你想在派生class中调用虚函数,你必须实例化一个派生class的对象并通过这个对象或指针(类型可能是基class) 到这个对象。

基类和派生类class中的虚函数表是分开的,因此您不能通过基类class

的对象访问派生类class的虚函数

所以这是特定于 Wikipedia notes 的实现:

The C++ standards do not mandate exactly how dynamic dispatch must be implemented, but compilers generally use minor variations on the same basic model.

Typically, the compiler creates a separate vtable for each class. When an object is created, a pointer to this vtable, called the virtual table pointer, vpointer or VPTR, is added as a hidden member of this object. The compiler also generates "hidden" code in the constructor of each class to initialize the vpointers of its objects to the address of the corresponding vtable.

更糟Danny Kalev states:

Compilers can be divided into two categories with respect to their vptr's position. UNIX compilers typically place the vptr after the last user-declared data member, whereas Windows compilers place it as the first data member of the object, before any user-declared data members.

我给出了所有前面的信息来表明我的 hack 可以工作的条件:

  1. is_standard_layout 对您的 child 失败,因为它有:"has virtual functions or virtual base classes"
  2. 你的 parent 没有 有复制构造函数或赋值运算符(否则你可以在 parent 的构造中复制 child object)
  3. 您的编译器确实实现了 v-table
  4. 您的 v-table 是在 object 布局的开头还是结尾
  5. 您的编译器分配给 child 成员变量的位置相对于 object 布局中的 parent 成员变量

了解了您遇到的黑客攻击后,我现在将继续扩展问题中的 类 以更好地展示我们如何 "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 来复制 parent object 的状态,同时允许所有 Child 信息保留。

可以看到这段代码:

Parent foo(13);
Child bar(&foo);

bar.func();

将打印:

Child, parent says: 13

Live example and though it's not asked for in the question here's how it could be accomplished for multiple inheritance: http://ideone.com/1QOrMz

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

  • ""
  • "C"
  • "POSIX"

我想通过指出整个 post 是关于如何克服 moneypunct 的设计者有意设置的限制来结束这个回答。所以,是的,你可以这样做,但当你这样做时,应该问一个明显的问题:"Why were the copy constructor and assignment operator of moneypunct deleted in the first place? What aspect of that design am I intentionally subverting?"