3

在 C++ 中,假设以下类层次结构:

class BaseClass { };
class ChildClass : public BaseClass { };

进一步假设这两个类的工厂类具有一个通用的模板化基类:

template<typename T>
class Factory {
public:
  virtual T* create() = 0;
};

class BaseClassFactory : public Factory<BaseClass> {
public:
  virtual BaseClass* create() {
    return new BaseClass(&m_field);
  }
private:
  SomeClass m_field;
};

class ChildClassFactory : public Factory<ChildClass> {
public:
  virtual ChildClass* create() {
    return new ChildClass(&m_field);
  }
private:
  SomeOtherClass m_field; // Different class than SomeClass
};

请注意, 和 的大小/内部结构因字段不同ChildClassFactoryBaseClassFactory有所不同。

现在,如果 a 有ChildClassFactory(or Factory<ChildClass>) 的实例,我可以安全地将它转换为Factory<BaseClass>(via reinterpret_cast) 吗?

Factory<ChildClass>* childFactory = new ChildClassFactory();

// static_cast doesn't work - need to use reinterpret_cast
Factory<BaseClass>* baseFactory = reinterpret_cast<Factory<BaseClass>*>(childFactory);

// Does this work correctly? (i.e. is "cls" of type "ChildClass"?)
BaseClass* cls = baseFactory->create();

我知道你不能总是以这种方式转换模板类,但在这种特殊情况下,转换应该是安全的,不是吗?

我已经用 Visual C++ 2010 对其进行了测试,它确实有效。我现在的问题是这是否可以移植到其他编译器?

更新:由于存在一些混淆,让我澄清一下在我的示例中什么(应该是)重要的:

  • ChildClass是一个子类BaseClass
  • 的用户Factory<BaseClass>不知道BaseClass将创建哪个子类。他只知道那BaseClass是被创造出来的。
  • Factory<T>没有自己的字段(除了 vtable)。
  • Factory::create()virtual
4

3 回答 3

5

不它不是。除了一些特殊情况外,您不能使用 a reinterpret_castother 的结果来回退内容:

ISO14882:2011(e) 5.2.10-7:

对象指针可以显式转换为不同类型的对象指针。70 当“指向 T1 的指针”类型的纯右值 v 转换为“指向 cv T2 的指针”类型时,结果为 static_cast(static_cast(v))如果 T1 和 T2 都是标准布局类型 (3.9) 并且 T2 的对齐要求不比 T1 更严格,或者任何一个类型都是无效的。将“指向 T1 的指针”类型的纯右值转换为“指向 T2 的指针”类型(其中 T1 和 T2 是对象类型,并且 T2 的对齐要求不比 T1 的对齐要求更严格)并返回其原始类型会产生原始类型指针值。未指定任何其他此类指针转换的结果。

为了使可能的失败场景更加清晰,请考虑多重继承,其中使用static_castordynamic_cast有时会调整指针值,但reinterpret_cast不会。考虑在这个例子中从A*to 转换B*

struct A { int x; };
struct B { int y; };
struct C : A, B { };

要了解您的代码如何以不同的方式失败,请考虑大多数编译器如何实现虚拟函数调用机制:使用虚拟指针。您的实例ChildClassFactory将有一个虚拟指针,指向ChildClassFactory. 现在当你reinterpret_cast这个野兽时,它只是偶然“工作”,因为编译器需要一些虚拟指针,指向一个具有相同/相似布局的虚拟表。但它仍将包含指向ChildCLassFactory虚函数的值,因此将调用这些函数。所有这一切都是在调用未定义的行为之后很久。就好像你开着车跳进一个大峡谷,想着“嘿,一切都很好”,只是因为你还没有着地。

于 2012-01-26T13:40:37.400 回答
0

不,reinterpret_cast 仅用于低级代码,因为它不会执行正确的地址操作。请改用 static_cast 或 dynamic_cast,

为什么要两个工厂,这不符合 GoF 工厂模式。

reinterpret_cast 不是这样做的方法,因为它很慢(运行时检查)并且不是一个好的 OO 设计(你想使用语言中的多态性)。

而是在工厂类中创建构造函数来生成您所追求的类型,然后让这些构造函数调用各个类型的构造函数。

工厂模式允许您对实现中的更改一无所知,这是一件好事,因为您可以最大限度地减少依赖关系,并允许在未来的代码中更轻松地维护。

于 2012-01-26T13:41:09.663 回答
0

我已经勾选了上面的原始答案(给他功劳),但我想我会总结一下我在这里学到的东西。

所以,基本问题是没有定义必须如何实现调度虚拟调用。

这意味着内部用于虚拟呼叫调度的数据结构(例如 vtables)在从同一模板创建的模板实例之间可能或可能不兼容。

于 2012-01-26T15:06:51.853 回答