在运行时动态更改虚拟指针

发布于 2024-12-13 16:00:55 字数 145 浏览 2 评论 0 原文

假设我有两个类继承了一个具有纯虚函数的基类。这两个类都实现了各自版本的该函数,但不添加额外的成员变量,因此它们具有相同的大小。现在有时,在程序执行过程中,我想将一个类转换为另一个类,而不复制其所有数据。所以基本上我想让它使用其他类的虚拟表。有没有一种便携的方法可以做到这一点?

So let's say I have two classes that inherit a base class that has a pure virtual function. Both of the classes implement their own version of that function, but don't add additional member variables so they have the same size. Now sometimes, in the middle of the execution of the program, I want to convert one class to the other without copying all its data. So basically I want to make it use the virtual table of the other class. Is there a portable way of doing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

甜妞爱困 2024-12-20 16:00:55

执行此操作的可移植方法是实现您自己的类系统,该系统实际上具有可以复制的虚拟指针。

标准 C++ 中不存在虚拟指针这样的东西。

The portable way to do this would be to implement your own class system that actually has virtual-pointers that can be copied.

There is no such thing as a virtual-pointer in standard C++.

累赘 2024-12-20 16:00:55

挪威安德森咨询公司(现为埃森哲)的一位年轻同事曾经向我提出一个严重的问题。他们的应用程序是用 Visual Basic 开发的,需要很长时间才能加载。他怀疑这可能是因为他们把每个类都放在了自己的DLL中?

由于担心出现最坏的情况,我进一步询问。是的,他们也遇到了任意崩溃等问题。

他怀疑其他莫名其妙的崩溃可能与他们通过替换 vtable 指针在运行时更改对象类型的巧妙方案有关?

我建议也许他们不应该真的做这些事情。他怀疑地看着我,并大胆地说他们没有时间再从头开始做事情。事实上,他们已经在扩展它,并且存在各种问题,例如他们的项目负责人坚持要求他们在客户现场工作而不是参加强制性会议。对我来说,这听起来就像蘑菇管理(将它们放在黑暗中,当头弹出时,将其切掉):这些事情通常是相辅相成的。

无论如何,我给你同样的建议:不要。

也许您可以实施快速移动操作来将数据从a移动到b

或者,也许您会发现这都是一个过早优化的情况?

One young colleague at Andersen Consulting (now Accenture) in Norway once approached me with a serious problem. Their applications, developed in Visual Basic, took a heck of a long time to load. He suspected that this might be because they put each class in its own DLL?

Fearing the worst, I inquired further. And yes, they also had problems with arbitrary crashes etc.

He suspected that the otherwise inexplicable crashes might be connected to their ingenious scheme for changing the type of an object at runtime, by replacing the vtable pointer?

I suggested that maybe they shouldn't really do these things. He looked skeptically at me, and ventured that they didn't have time to do things from scratch again. In fact, they were already stretching it, and there were various problems with that, like their project leader insisting that they work at the client site instead of participating at obligatory meetings. To me, that sounded like mushroom management (keep them in the dark, when a head pops up, cut it): these things often go together.

Anyway, I give you the same advice: don't.

Perhaps you can instead implement fast move operations to move data from a to b?

Or, perhaps you will discover that it's all a case of premature optimization?

俏︾媚 2024-12-20 16:00:55

没有。就语言而言,不存在虚拟表这样的东西,更不用说关于它的外观/包含什么/存储位置的规则了。

某种形式的构图可能更适合您的任务。

Nope. As far as the language is concerened, there is no such thing as a virtual table, let alone rules about how it looks/what it contains/where it is stored.

Some form of composition is probably more appropriate to your task.

記柔刀 2024-12-20 16:00:55

有没有一种便携的方法可以做到这一点?

绝对不是。规范没有定义虚拟函数如何实现的细节,因此没有可移植的方法来假装一个虚拟类是另一个虚拟类。

Is there a portable way of doing this?

Absolutely not. The specifics of how virtual functions are implemented are not defined by the spec, and therefore there is no portable way to pretend one virtual class is another.

稚气少女 2024-12-20 16:00:55

正如其他答案所说,实际上更改 vtable 绝对是不可移植的。

但是,有几种解决方法可以让您在不实际更改类类型的情况下完成类似的语义:

这个最简单的解决方案是使用描述当前实现的枚举来“滚动您自己的”继承:

class MyClass
{
    public:
    enum DerivedType { A, B };

    private:
    DerivedType myType;

    public:
    void myVirtualFunction()
    {
        if (myType == A)
            myAFunction();
        else
            myBFunction();
    }
}

您还可以使用函数指针作为公共成员变量,设置为指示类类型的函数。然后您可以将函数指针设置为另一个类的函数以“更改它的类型”

既然您提到要避免复制数据,您可以保留不同的类,但具有指向所有成员变量的引用计数指针,这样您可以快速创建彼此相反类型的新对象。

As the other answers have said, actually changing the vtable is definitely non-portable.

However, there are several work-arounds that could let you accomplish similar semantics without actually changing the class type:

This simplest solution would be to "roll your own" inheritance with an enum describing the current implementation:

class MyClass
{
    public:
    enum DerivedType { A, B };

    private:
    DerivedType myType;

    public:
    void myVirtualFunction()
    {
        if (myType == A)
            myAFunction();
        else
            myBFunction();
    }
}

You could also use a function pointer as a public member variable, which is set to the function indicating the class type. Then you could set the function pointer to the other class's function to "change it's type"

Since you mention you want to avoid copying the data, you could keep your different classes, but have reference-counting pointers to all your member variables, so that you can create new objects of the opposite type from each other quickly.

你丑哭了我 2024-12-20 16:00:55

使用新的展示位置怎么样?这可能不太便携,但它确实完成了所需的事情 - 替换 vtable,仅此而已。只需要处理构造函数 - 使用一个空的构造函数。

struct Base
{
    int someData;
    virtual int GetValue() = 0;
};

struct A : public Base
{
    int GetValue() override { return 11111; }
};

struct B : public Base
{
    int GetValue() override { return 22222; }
};

A ob;
ob.someData = 123;
auto ob2 = new (&ob) B;
auto value = ob2->GetValue();

没有提及明显的事情,例如班级规模、最佳实践等。

What about using placement new? This is maybe not quite portable, but it does exactly the required thing - replaces vtable, and nothing more. Just need to take care of constructor - use an empty one.

struct Base
{
    int someData;
    virtual int GetValue() = 0;
};

struct A : public Base
{
    int GetValue() override { return 11111; }
};

struct B : public Base
{
    int GetValue() override { return 22222; }
};

A ob;
ob.someData = 123;
auto ob2 = new (&ob) B;
auto value = ob2->GetValue();

Not mentioning obvious things like classes size, best practices, etc.

ヤ经典坏疍 2024-12-20 16:00:55

尽管这个问题很老了,但我想提出一种方法来做到这一点。 (不太确定可移植性)

据我了解,您有一个类 BC 继承自某个类 A 并且只有一个它们之间存在虚函数。 (如果 BC 也不相关,我在这里介绍的方法也有效。)

class A {
public:
    virtual std::string hello() = 0;
};

class B : public A { 
public:
    virtual std::string hello() { return "B"; }
};

class C : public A {
public:
    virtual std::string hello() { return "C"; }
};

然后您想将 B 带到 < code>C 然后调用 hello 并获取 "B"


因此,有一种方法可以创建 boost::any 的淡化版本,只要适合就可以将任何内容转换为任何内容:)

struct parent {};

template< typename T >
struct child : public parent {
    child(T const& t): item(t){}
    mutable T item;
};

template< typename T >
T& as(parent const & p) { return static_cast< child< T > const& >(p).item; }

然后将它们混合在一起:

B b;
parent* p = new child< B >(b);
std::cout << as< C >(*p).hello() << std::endl;
// ==== OUTPUT ====
// B

可以看到正在运行的代码此处


更进一步,我们可以创建一个从一种类型转换为另一种类型的函数,而不需要让小昆虫知道它们之间发生了什么。

template< typename TO, typename FROM >
TO& convert(FROM const& from) {
    parent* p = new child< FROM >(from);
    return as< TO >(p);
};

可以在此处运行。

(意识到我错过了这些示例代码链接中的继承,但在阅读问题后,我认为这就是真正想要的。因此,要查看没有继承的测试,请转到 这里


我开始使用的一些其他代码我认为也可能对一些人有所帮助......

#include <iostream>
#include <string>

class B {
public:
    virtual char hello() {return 'B';}
};

class C {
public:
    virtual int hello() {return 65;}
};

struct parent {};

template< typename T >
struct child : public parent {
    child(T const& t): item(t){}
    mutable T item;
};

template< typename T >
T& as(parent const & p) { return static_cast< child< T > const& >(p).item; }

template< typename TO, typename FROM >
TO& convert(FROM const& from) {
    parent* p = new child< FROM >(from);
    return as< TO >(*p);
};

int main()
{
    B b;
    std::cout << convert< C, B >(b).hello() << std::endl;
    C c;
    std::cout << convert< B, C >(c).hello() << std::endl;
}
// ==== OUTPUT ====
// 66
// A

弄清楚如何在转换函数中实现这一切:

template< typename TO, typename FROM >
TO& convert(FROM const& from) {
    struct parent {};

    struct child : public parent {
        child(FROM const& t): item(t){}
        mutable FROM item;
    };

    struct sibling : public parent {
        sibling(TO const& t): item(t){}
        mutable TO item;
    };

    parent* p = new child(from);
    return static_cast< sibling const& >(*p).item;
};

Even though this question is old, I would like to bring up a way to do this. (Not quite sure on the portability)

From what I understand you have a class B and C that inherit from some class A and only a single virtual function exists between them. (The method I present here works if B and C are not related as well.)

class A {
public:
    virtual std::string hello() = 0;
};

class B : public A { 
public:
    virtual std::string hello() { return "B"; }
};

class C : public A {
public:
    virtual std::string hello() { return "C"; }
};

And then you want to take a B to a C then call hello and get "B".


So, there is a way to create a watered down version of the boost::any that will cast anything to anything as long as it fits:)

struct parent {};

template< typename T >
struct child : public parent {
    child(T const& t): item(t){}
    mutable T item;
};

template< typename T >
T& as(parent const & p) { return static_cast< child< T > const& >(p).item; }

Then mix it all together:

B b;
parent* p = new child< B >(b);
std::cout << as< C >(*p).hello() << std::endl;
// ==== OUTPUT ====
// B

Can see the code in action here.


To go a step further we can create a function that converts from one type to another without giving a gnat's rear-end about what goes on between them.

template< typename TO, typename FROM >
TO& convert(FROM const& from) {
    parent* p = new child< FROM >(from);
    return as< TO >(p);
};

This can be ran here.

(Realized that I missed the inheritance in these example code links, but after reading the question I think that is what was actually desired. So, to see the test without inheritance go here)


Some other code that I started playing with that I thought might help some as well...

#include <iostream>
#include <string>

class B {
public:
    virtual char hello() {return 'B';}
};

class C {
public:
    virtual int hello() {return 65;}
};

struct parent {};

template< typename T >
struct child : public parent {
    child(T const& t): item(t){}
    mutable T item;
};

template< typename T >
T& as(parent const & p) { return static_cast< child< T > const& >(p).item; }

template< typename TO, typename FROM >
TO& convert(FROM const& from) {
    parent* p = new child< FROM >(from);
    return as< TO >(*p);
};

int main()
{
    B b;
    std::cout << convert< C, B >(b).hello() << std::endl;
    C c;
    std::cout << convert< B, C >(c).hello() << std::endl;
}
// ==== OUTPUT ====
// 66
// A

Figured out how to make it all within the convert function:

template< typename TO, typename FROM >
TO& convert(FROM const& from) {
    struct parent {};

    struct child : public parent {
        child(FROM const& t): item(t){}
        mutable FROM item;
    };

    struct sibling : public parent {
        sibling(TO const& t): item(t){}
        mutable TO item;
    };

    parent* p = new child(from);
    return static_cast< sibling const& >(*p).item;
};
酒绊 2024-12-20 16:00:55

正如其他人已经指出的那样:C++ 不应该以这种方式使用。为什么? C++ 基于思想类型描述行为的静态方面,因此不应该被操纵。这允许在编译时进行大量检查,因为有关类型的所有内容都是事先已知的。

PImpl 模式

然而,有一种众所周知的、可移植的且普遍接受的设计模式,它可以为您提供所需的行为:指向实现的指针(“PImpl”)。

  • 有一个接口类作为前端;这公开了您的客户端应该调用的函数,
  • 此类持有一个指向实现类的私有指针,并将每个调用从前端函数转发到实现类上的适当函数。

对于您想要的用例,您可以提供额外的 API,这使得前端对象切换其内部实现对象,从而导致动态行为变化。

由于“PImpl”指针是私有的,因此您可以自由地使用各种巧妙的技巧,并且可以完全在前端类的实现中(在 *.cpp 文件中)执行此操作。您可以将它们放入内联缓冲区(新放置),或者您可以将它们作为单例保存在单独的管理器中,或者您可以使用实例池和块分配方案 - 无论您需要满足特定的性能目标。

在最简单的形式中,PImpl 只是一个具有单一所有权的智能指针,并且实现对象维护在堆上。

class Interface
{
public:
  virtual ~Interface() {}
  
  virtual void doIt()  =0;
};

class ImplA : public Interface
{
  void doIt()  override  { /* impl A */ }
};

class ImplB : public Interface
{
  void doIt()  override  { /* impl B */ }
};



class FrontEnd
{
  std::unique_ptr<Interface> pimpl_;
  
public:
  FrontEnd()
    : pimpl_{new ImplA()}
  { }

  void doIt()    { pimpl_->doIt(); }
  
  void switchB() { pimpl_.reset(new ImplB();) }
  
};

As others have already pointed out: C++ is not meant to be used this way. Why? C++ is based on the idea type describes the static aspects of behaviour, and thus should not be manipulated. This allows to do a lot of checks at compile time, since everything about a type is known beforehand.

PImpl pattern

However, there is a well known, portable and generally accepted design pattern, which can give you the desired behaviour: pointer-to-implementation ("PImpl").

  • have a interface class as front-end; this exposes the functions your clients should invoke
  • this class holds a private pointer to an implementation class, and forwards each call from the front-end functions to appropriate functions on the implementation class.

For your desired use case, you could provide an additional API, which makes the front-end object switch its internal implementation object, leading to a dynamic behaviour change.

Since the "PImpl" pointer is private, you're free to pull off all kinds of clever tricks, and you can do so completely within the implementation of the front-end class (in the *.cpp file). You could place them into an inline buffer (with placement new), or you could hold them as singletons in a separate manager, or you could use a pool of instances and a block allocation scheme -- whatever you need to meet specific performance goals.

In the most simplest form, the PImpl is just a smart pointer with single ownership, and the implementation objects are maintained on heap.

class Interface
{
public:
  virtual ~Interface() {}
  
  virtual void doIt()  =0;
};

class ImplA : public Interface
{
  void doIt()  override  { /* impl A */ }
};

class ImplB : public Interface
{
  void doIt()  override  { /* impl B */ }
};



class FrontEnd
{
  std::unique_ptr<Interface> pimpl_;
  
public:
  FrontEnd()
    : pimpl_{new ImplA()}
  { }

  void doIt()    { pimpl_->doIt(); }
  
  void switchB() { pimpl_.reset(new ImplB();) }
  
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文