投射到 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();
};
我可以使用默认构造函数构造 Parent
或 Child
,但不会设置 Complex members
。所以说我得到了 Parent foo
,它是使用自定义构造函数构造的,我想将 foo
object 与 Child
的 func
方法一起使用。我怎么做?直接 dynamic_cast<Child*>(&foo)
段错误,所以可能没有办法:http://ideone.com/JcAaxd
auto bar = dynamic_cast<Child*>(&foo);
我是否必须创建一个接受 Parent
并在内部复制它的 Child
构造函数?或者有什么方法可以将 bar
变为存在?
编辑:
为了深入了解我的实际问题,示例中的 Parent
是 moneypunct
,它已在标准中实现,因此我无法修改它。
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;
}
注意几件事:
- 友谊不会继承,因此您必须列出所有 children 到 Parent 您打算使用的助手。
- 但是,它确实为您提供了一种按原样访问 Parent class 的所有数据成员的方法,它不会导致一些奇怪的行为。
- 这可能仍然不是您要查找的内容,但根据您的问题,我认为它可能有所帮助。
将基对象的地址分配给派生对象的指针是不合法的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.
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 可以工作的条件:
is_standard_layout
对您的 child 失败,因为它有:"has virtual functions or virtual base classes"
- 你的 parent 没有 有复制构造函数或赋值运算符(否则你可以在 parent 的构造中复制 child object)
- 您的编译器确实实现了 v-table
- 您的 v-table 是在 object 布局的开头还是结尾
- 您的编译器分配给 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?"
我实际上想做的是将构造的 moneypunct
转换为 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();
};
我可以使用默认构造函数构造 Parent
或 Child
,但不会设置 Complex members
。所以说我得到了 Parent foo
,它是使用自定义构造函数构造的,我想将 foo
object 与 Child
的 func
方法一起使用。我怎么做?直接 dynamic_cast<Child*>(&foo)
段错误,所以可能没有办法:http://ideone.com/JcAaxd
auto bar = dynamic_cast<Child*>(&foo);
我是否必须创建一个接受 Parent
并在内部复制它的 Child
构造函数?或者有什么方法可以将 bar
变为存在?
编辑:
为了深入了解我的实际问题,示例中的 Parent
是 moneypunct
,它已在标准中实现,因此我无法修改它。
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;
}
注意几件事:
- 友谊不会继承,因此您必须列出所有 children 到 Parent 您打算使用的助手。
- 但是,它确实为您提供了一种按原样访问 Parent class 的所有数据成员的方法,它不会导致一些奇怪的行为。
- 这可能仍然不是您要查找的内容,但根据您的问题,我认为它可能有所帮助。
将基对象的地址分配给派生对象的指针是不合法的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.
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 可以工作的条件:
is_standard_layout
对您的 child 失败,因为它有:"has virtual functions or virtual base classes"- 你的 parent 没有 有复制构造函数或赋值运算符(否则你可以在 parent 的构造中复制 child object)
- 您的编译器确实实现了 v-table
- 您的 v-table 是在 object 布局的开头还是结尾
- 您的编译器分配给 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?"