【问题标题】:What type of cast is appropriate here?什么类型的演员适合这里?
【发布时间】:2015-09-09 10:10:36
【问题描述】:

我有一个名为 BaseNode 的抽象类模板,定义如下

template<class T>
class BaseNode
{
public:
    T* addChildNode(const char *name);
    void deleteChildNode(const char *name);
    void deleteAllChildNodes();

    T* findFirstNode(const char *name);
    T* getChildNode(const char *name);
    T* getChildNode(unsigned int index);

    void setName(const char *name);
    void setTranformation(const glm::mat4 &transformation);

    unsigned int getNumChildren() const { return _children.size(); }
    const char *name() const { return _name.c_str(); }
    T* parent() const { return _parent; }
    const glm::mat4& transformation() const { return _transformation; }
    const glm::mat4& toRootTransformation() const { return _toRoot; }

protected:
    BaseNode(const char *nodeName, T *parent);
    virtual ~BaseNode();

    std::string _name;

    glm::mat4 _transformation;
    glm::mat4 _toRoot;

    T *_parent;
    std::vector<T*> _children;
};

这个类的功能是我可以创建自己类型的“节点类”,然后它们会继承这个类模板的所有场景图方法。 (例如class MyNode : public BaseNode&lt;MyNode&gt; {...};

addChildNode(const char *name) 函数将 new T(name, this) 插入到子向量中,而 this 应该是子向量的父级。编译器抛出一个转换错误并建议我为此使用某种类型的转换。我目前正在使用dynamic_cast,但这真的很困扰我,因为我认为它没用。

我的问题是在这种情况下使用 reinterpret_cast(基本上就像 C-cast ..?)是否安全:_children.push_back(new T(name, reinterpret_cast&lt;T*&gt;(this))); 因为 IMO dynamic_cast 永远不会失败。

【问题讨论】:

  • static_cast 最接近 C-cast。
  • @TartanLlama 看看 MyNode 示例。
  • 不要重新解释从父指针到子指针的转换,反之亦然。这是 UB,如果您有多重继承,它肯定会因某些基类而失败。请改用 static_cast。实际上更喜欢 dynamic_cast,除非您知道它会在您的程序中导致显着的性能损失。仅使用 reinterpret_cast 将您的指针投射到(可能是 cv-qualified)void*char* 并返回。
  • @n.m 仅当怀疑对象的运行时类型是否与 static_cast 兼容时,我才会使用 dynamic_cast。 C++ 程序几乎在任何地方都存在固有的程序员错误。编码效率低下(即使效率不重要)以关闭一个微小的程序员错误来源,是愚蠢的。
  • @JSF 愚蠢的是一个生产系统会遇到 UB、失控并破坏客户数据,而不是在一个复杂的、未经正式验证的系统中检测和报告逻辑错误。

标签: c++ oop templates


【解决方案1】:

这是Curiously recurring template pattern 的一个例子,所以static_cast 在这里是惯用的。

在这种情况下,您可以通过多种方式避免强制转换。最简单的就是把构造函数改成:

BaseNode(const char* nodeName, T* parent, T* thisAsT);

并添加适当的字段:

//...
T* _parent;
T* _thisAsT;

因此,在addChildNode 中,您将可以轻松访问具有适当类型的this 指针:

_children.push_back(new T(name, _thisAsT));

这当然要求您的派生类在其构造函数中为此参数提供有效指针。


再补充一个,不过有点笨拙:

virtual T* buildFromName(const char* name) = 0;

然后在addChildNode中使用:

_children.push_back(buildFromName(name));

然而,这需要派生类为其自己的类型实现工厂方法,例如,这违反了SRP。另一方面,为每个派生类创建工厂类型似乎有点过头了。


第二个想法,与上面类似的属性:

virtual T* getThis() = 0;

然后:

_children.push_back(new T(name, getThis()));

附注:

- 考虑使用std::string 而不是const char*

- 声明应该写成T* x 而不是T *x(也应该是const char* x 而不是const char *x) - 星号不是变量名的一部分,它是类型说明符的一部分,所以应该在“类型方面”

【讨论】:

  • 感谢您的回答。我将只使用 static_cast。关于你的第二个说明:我就是喜欢这个约定。它强调变量的“指针”属性。用名字写星号也更“正确”,因为当输入像int* p1, p2; p2 这样的东西时,实际上是一个 int,而不是 int*。那只是我:P
【解决方案2】:

static_cast 是正确的。
dynamic_cast 比您需要的要慢得多。 reinterpret_cast 不正确。您不知道派生对象的基类部分与派生对象的起始位置相同。如果需要,静态转换会调整地址。 reinterpret_cast 不会。

【讨论】:

  • 以百分比表示,dynamic_cast 比编译时间转换慢多少?
  • @pilpel 对于演员表本身,static_cast 通常需要零指令和零时间。 dynamic_cast 永远不会是零时间。百分比从几千% 到无穷大变化很大。即使 % 是无限的,我们仍然可能不会谈论很多时间。但是我遇到过将不必要的 dynamic_cast 更改为 static(在循环内)节省了数小时的总执行时间的情况。
【解决方案3】:

dynamic_cast 和 reinterpret_cast 仍然不同。如果基类和派生类的大小不同,则指向同一对象的基类指针和派生类指针有可能具有不同的地址 - 如果存在虚拟表。

见:About dynamic cast and address of base and derived objects

如果没有虚拟方法,则使用 static_cast -> 编译时强制转换(执行速度更快)。

【讨论】:

  • 即使有虚方法,static_cast 也是快速且正确的。动态转换较慢(有或没有虚拟方法)。当 reinterpret_cast 比 static_cast 快一点时, reinterpret_cast 必然是错误的。在 reinterpret_cast 工作的情况下(尽管技术上仍然不正确),static_cast 将生成与 reinterpret_cast 相同的代码。
猜你喜欢
  • 2021-11-16
  • 1970-01-01
  • 2019-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-07
  • 1970-01-01
相关资源
最近更新 更多