是否可以“专门化”基础对象以成为派生对象?
例如:
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()
}
答案 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
成员无需调用正确的方法,编译器已为您处理此问题。