C++ 使用C++;,是否有一种安全合法的方法来实施“;安全铸造&x201D;对于一个涉及多个和虚拟继承的类层次结构,没有RTTI?
我目前正在使用元类型系统为我的项目的一小部分实现“安全转换”,因为a)RTTI和dynamic_cast在我的项目环境中被故意禁用,b)这样的功能将极大地简化和澄清代码 在我的项目中,有一个多级类层次结构,在某些情况下具有多重继承。然而,有一个“根”类实际上是由所有其他类直接或间接继承的。(这是层次结构中唯一的虚拟继承。)是否有一种安全、合法的方法在这样的层次结构中执行降级,而不使用RTTI/dynamic_cast?我在网上搜索过,考虑过几种方法,但似乎没有一种是中肯的。在我的例子中,类层次结构是已知的——每个对象都知道它们的(元)类型和它们的祖先,基本上通过使用标记/枚举。(是否必须知道实现“安全强制转换”所需的其他信息,如基类初始化顺序) 我尝试的一种方法是通过返回“this”指针的虚拟函数调用进行向下转换。从我读到的(以及我自己的经验),我明白C++中的“这个”指针是与定义成员函数的类相对应的类型,其中包含了应用到它的成员函数的CV资格。(如此处所述=>)。我试图通过返回“this”指针来获取具有对象的动态(运行时)类型的指针。因为我希望我的所有对象都实现这个函数,所以我在基类中将它设为纯虚函数,然后在派生类中使用协变返回类型(实现类的指针)定义它。通过基指针进行调度的工作与预期一样,但返回的指针的类型似乎基于调用指针,而不是调用实现的类型。C++ 使用C++;,是否有一种安全合法的方法来实施“;安全铸造&x201D;对于一个涉及多个和虚拟继承的类层次结构,没有RTTI?,c++,casting,dynamic-cast,downcast,static-cast,C++,Casting,Dynamic Cast,Downcast,Static Cast,我目前正在使用元类型系统为我的项目的一小部分实现“安全转换”,因为a)RTTI和dynamic_cast在我的项目环境中被故意禁用,b)这样的功能将极大地简化和澄清代码 在我的项目中,有一个多级类层次结构,在某些情况下具有多重继承。然而,有一个“根”类实际上是由所有其他类直接或间接继承的。(这是层次结构中唯一的虚拟继承。)是否有一种安全、合法的方法在这样的层次结构中执行降级,而不使用RTTI/dynamic_cast?我在网上搜索过,考虑过几种方法,但似乎没有一种是中肯的。在我的例子中,类层次结
下面是说明问题的代码:
#include <iostream>
struct Base {
virtual ~Base(){}
virtual Base* foo( void ) = 0 ;
} ;
struct Derived : public Base { // <= XXX
Derived* foo( void ){ std::cout << "In Derived::foo()" << std::endl ; return this ; }
void foo2( void ) { std::cout << "In Derived::foo2()" << std::endl ; }
} ;
int main ( int argc, char* argv[] ) {
Base* base = new Derived ;
base->foo( ) ; // Dispatches to Derived::foo()
// Derived* derived = base->foo( ) ; // PROBLEM!
Derived* derived = static_cast< Derived* >( base->foo( ) ) ; // Works, as long as inheritance at XXX is non-virtual
derived->foo2( ) ;
delete base ;
return 0 ;
}
我曾尝试过使用icc、clang和g++的版本,并得到了类似的结果,因此我相信这不是一个编译器错误,我遗漏了一些非常基本的东西
从具有协变返回类型的虚拟成员函数定义返回的“this”指针的类型是什么?当我通过基*调用foo()时,我显然执行了派生::foo()的实现,并且该实现成员函数被声明为返回派生*。那么,为什么从返回的指针执行赋值需要向下转换呢?使用static_cast是一个问题,因为我的项目代码中确实有一个实际上继承的基类,static_cast将落在这一点上。我的印象是,使用C样式转换或reinterpret_转换从基类向下转换是不安全的(至少在一般情况下是这样),因为它们对对象数据/虚拟表布局是“盲”的。通过遵循一些规则/限制,这些可以变得“安全”吗?协变返回类型允许子类返回与基类返回类型不同但继承自基类返回类型的类型。这允许您在从派生类调用时获得不同的返回值,但在通过基类调用时获得正确的类型 因此,在您的示例中,
Derived
返回的值属于Derived
类型,但是当您通过Base
指针调用该函数时,编译器必须在编译时决定返回值是否可以合法赋值。在运行时,将调用foo()
的正确实例,因为foo()
声明为虚拟,但编译器必须使用相同的返回类型,无论在运行时实际使用的是Base
的哪个子类。这就是协变返回类型不能完全任意的原因。也就是说,在基类中不能返回int
,在派生类中不能返回字符串。派生类的返回类型必须是基类中返回的类型的子类型
这里可能值得注意的是,虽然您能够强制转换返回类型并正确编译所有内容,但您也可以强制转换指针并获得正确的值:
Derived* derived = static_cast< Derived* >(base)->foo( ); // OK
Derived*Derived=static_cast(base)->foo();//好啊
现在回答您最初的问题,如果您的目标是从未知类型的基指针开始,向下转换到派生指针,那么您将需要一些运行时信息。RTTI就是这样做的,它使用存储在vtable中的信息来确定实际的类型。如果您没有RTTI,但有一些嵌入式枚举,那么您当然可以使用它。我看不到你的课程,但类似这样的东西可以:
class Base
{
int myId;
protected:
Base(int id) : myId(id)
public:
template <class T>
T* downcast()
{
if (myId == T::ID) { return static_cast<T*>(this); }
return nullptr;
}
};
class Derived1
{
public:
static const int ID = 1;
Derived1() : Base(ID) {}
};
class Derived2
{
public:
static const int ID = 2;
Derived2() : Base(ID) {}
};
Base* b = new Derived1();
Derived1* d1 = b->downcast<Derived1>(); // Ok, returns a value
Derived2* d2 = b->downcast<Derived2>(); // returns nullptr
类基
{
int-myId;
受保护的:
Base(int-id):myId(id)
公众:
模板
T*downcast()
{
if(myId==T::ID){return static_cast(this);}
返回空ptr;
}
};
派生类1
{
公众:
静态常量int ID=1;
Derived1():基(ID){}
};
派生类2
{
公众:
静态常量int ID=2;
Derived2():基(ID){}
};
Base*b=新的Derived1();
Derived1*d1=b->downcast();//好的,返回一个值
Derived2*d2=b->downcast();//返回空ptr
注意:这是为了回答我以为你在问的问题,但我并不认为这是个好主意。正如评论中所建议的,我更喜欢使用访问者模式,而不是强制转换和检查类型,或者在虚拟方法中实现行为,或者其他一些事情,即使我确实有可用的RTTI
更多说明:整个解决方案的要点是基于这样一个假设,即您持有一个基*
,并希望将其转换为一个派生*
,但您不确定所持有的对象是否实际上是一个D
class Base
{
int myId;
protected:
Base(int id) : myId(id)
public:
template <class T>
T* downcast()
{
if (myId == T::ID) { return static_cast<T*>(this); }
return nullptr;
}
};
class Derived1
{
public:
static const int ID = 1;
Derived1() : Base(ID) {}
};
class Derived2
{
public:
static const int ID = 2;
Derived2() : Base(ID) {}
};
Base* b = new Derived1();
Derived1* d1 = b->downcast<Derived1>(); // Ok, returns a value
Derived2* d2 = b->downcast<Derived2>(); // returns nullptr