“专门化”基础对象成为派生对象?

时间:2016-01-02 19:48:32

标签: c++ c++11 object inheritance syntax

是否可以“专门化”基础对象以成为派生对象?

例如:

class base{...
  base(...) : ... {}//both have their own constructors
  virtual void foo(){}
};

class derived : public base{...
  void foo() override; //actual function in cpp
...};

int main(){
  base x;
  //is it possible to do something to x so
  x.foo(); // will call derived::foo()
}

3 个答案:

答案 0 :(得分:3)

 //is it possible to do something to x so
 x.foo(); // will call derived::foo()

不,你不能,至少不能以便携和符合标准的方式。

但是,您可以从derived创建x,然后再使用它。

首先,在derived中添加一个以base为参数的构造函数。

class derived : public base{
  derived(base const& b) { ... }
  void foo() override; //actual function in cpp
...}

现在使用它:

derived(x).foo();

答案 1 :(得分:2)

问题和简单的答案。

给定代码:

class base{...
  base(...) : ... {}//both have their own constructors
  virtual void foo(){}
};

class derived : public base{...
  void foo() override; //actual function in cpp
...};

int main(){
  base x;
  //is it possible to do something to x so
  x.foo(); // will call derived::foo()
}

问题是

  

“是否有可能"专业化"要成为派生对象的基础对象?

E.g。在上面的代码中更改x的动态类型。

在便携式标准C ++中,没有。

为什么它通常没有意义。

derived相比,

base通常会有其他数据成员,可能还有不同的类不变量。在base实例中访问那些缺少的数据成员,就好像该实例是derived一样,通常会产生未定义的行为。依赖于derived实例的base类不变量同样会造成严重破坏。

在特殊情况下如何以非移植方式完成。

据我所知,每个现存的C ++实现都通过在每个对象中存储 vtable指针(加上可能更多的信息)来实现虚函数机制。 vtable 是一个特定于类的单个表,指向虚函数实现的指针。通过更改对象的vtable指针,可以强制它像某个派生类一样运行,例如:

64位Windows程序的示例
class Base
{
public:
    virtual auto foo() -> int { return 111; }
    Base() {}
};

struct Derived
    : public Base
{
public:
    auto foo() -> int override { return 222; }
};

namespace cast {
    template< class Type >
    auto temp_ref( Type&& o ) -> Type& { return o; }
}

auto test( Base& o ) -> int { return o.foo(); }

#include <iostream>
auto main() -> int
{
    Base x;

    //is it possible to do something to x so
    auto const r1 = x.foo(); // will call derived base::foo()?

    // Yes, non-portably. Here for MingW g++ 64-bit.
    reinterpret_cast<void*&>( x ) =
        reinterpret_cast<void*&>( cast::temp_ref( Derived() ) );
    auto const r2 = x.foo();    // May still call base::foo()
    auto const r3 = test( x );  // More likely to call derived::foo()

    using namespace std;
    cout << r1 << " -> " << r2 << " -> " << r3 << endl;
}

在Windows中运行64位MingW,我得到以下结果:

111 -> 111 -> 222

形式上这是非常未定义的行为,并且由于源代码中的点处的编译器可以假设它知道特定对象的vtable指针,因此在某些情况下它可能神秘地为Not Work™。上面结果中的第二个111就是一个例子。即,这是不可靠,当我担任顾问时,我发现了一个使用这种技术的项目,尽管在Visual Basic中,结果出现了可怕的问题。

它也是不可移植的,因为vtable指针可以是不同的大小,例如32位和64位,编译器不需要将它们存储在相同的位置。为了实现更有保证的效果,需要标准的二进制布局。例如,Microsoft的COM技术提供了这样的二进制布局标准。

答案 2 :(得分:2)

标准C ++中没有办法改变任何现有对象的类型。如果你真的需要一个不同的类型,你可以做的唯一事情是构建新对象,或诉诸非便携式黑客,正如另外两个答案所示。幸运的是,在您的情况下,您不需要更改任何对象的类型。

根据评论,这里是一个简单的程序,说明如何将derived1保留为derived1,如何确保它永远不会转换为真实{{1}对象,因此虚拟方法按预期工作,而不必强制尝试将base转换回派生类:

base

输出:

base::print()
derived1::print()
derived2::print()
derived3::print()

请注意,#include <iostream> #include <list> #include <memory> struct base { virtual void print() { std::cout << "base::print()" << std::endl; } }; struct derived1 : base { virtual void print() { std::cout << "derived1::print()" << std::endl; } }; struct derived2 : base { virtual void print() { std::cout << "derived2::print()" << std::endl; } }; struct derived3 : base { virtual void print() { std::cout << "derived3::print()" << std::endl; } }; int main() { std::list<std::shared_ptr<base>> list; list.push_back(std::make_shared<base>()); list.push_back(std::make_shared<derived1>()); list.push_back(std::make_shared<derived2>()); list.push_back(std::make_shared<derived3>()); for (auto item : list) item->print(); } type内的base成员无需调用正确的方法,编译器已为您处理此问题。