为啥 static_cast 需要指针或引用?

Posted

技术标签:

【中文标题】为啥 static_cast 需要指针或引用?【英文标题】:Why does static_cast require pointers or references?为什么 static_cast 需要指针或引用? 【发布时间】:2014-04-03 17:10:14 【问题描述】:

我最近遇到了一种情况,我必须使用 static_cast 将父类强制转换为子类,因为我知道对象实例就是那个子类。我是根据 if 条件知道这一点的。

类似这样的:

parent* foo;
child* bar;
if(foo is instance of child class)
   bar = static_cast<child*>(foo)

我的问题是: 为什么 static_cast 总是需要指针?当我尝试使用非指针变量时,这不起作用。 一个例外似乎是原始数据类型。

这是因为每个指针都可以转换为 void* 吗?这就是 static_cast 的工作原理吗?

编辑:我忘了提到它适用于参考。因此,目前提出的问题是错误的。将问题重新定义为“为什么 static_cast 需要指针或引用?”

【问题讨论】:

请举例说明当您“尝试使用非指针变量”时什么不起作用 通常,必须从父级转换为子级是一种代码味道。您可能需要重新考虑您的设计。看看你是否可以使用虚拟成员函数来完成同样的事情。 @Dima 通常代码已经存在多年,因此重新考虑设计需要重写数千行(如果不是数万行)代码。因此,重新思考通常不是一种选择。我遇到了这个确切的问题,即 K_U 的指针和引用可以很好地转换,但是转换非指针变量不会在无法重新考虑或重写的代码上编译。我希望 SO 上的某个人能为这个主题提供一些启示。 @K_U 看看***.com/questions/53294643/…。至少对我有帮助。 【参考方案1】:

为什么 static_cast 总是需要指针?

运算符static_cast 不需要指针,也不需要引用。

C++ 标准 n3337 § 5.2.9/4:

否则,表达式 e 可以显式转换为类型 T 如果声明,则使用 static_cast&lt;T&gt;(e) 形式的 static_cast T t(e);对于一些发明的临时变量t (8.5),格式正确。 这种显式转换的效果与执行 声明和初始化,然后使用临时 作为转换结果的变量。表达式 e 用作 一个泛左值当且仅当初始化使用它作为泛左值。

parent* foo;
child* bar;
if(foo is instance of child class)
   bar = static_cast<child*>(foo)

当我尝试使用非指针变量时,这不起作用。

例如?你是怎么试过的? 如果你的意思是

child c;
parent p = static_cast<parent>( c);

然后这称为切片,这意味着p只会从来自父类的c中获取那些数据(父类的对象如何也接收子部分,因为子类是派生父类的补充数据?)。

【讨论】:

不是您引用的static_cast 最有趣的用法,因为这里static_cast 不是实现该功能所必需的。当decltype(e) _(t); 是一个有效的表达式时,一个更有趣的用法是static_cast&lt;T&gt;(e) 我最喜欢这个答案,因为它引用了标准。如果 OP 仔细阅读,那么他可能会弄清楚为什么他尝试使用静态强制转换失败。 嗨,我尝试了另一种方式...我试图从父实例为子实例分配一个值。 这是没有意义的,因为孩子是比父母“更大”的对象【参考方案2】:

本质上static_cast&lt;&gt; 总是使用尖括号之间提供的类型创建新的东西。考虑:

class base  int x; ;
class derived: public base  int y; ;

现在下面的代码将无法编译:

base *b;
derived *d = &static_cast<derived>(*b);   // wrong

原因很简单:这段代码尝试创建derived 的新实例并将base 传递给它的构造函数。如果derived 有它的构造函数,它将编译。例如:

class derived: public base

  int y;
  derived(const base &)
;

但现在你有一个临时的,将被立即删除。

显然您不想在这里创建新的derived 实例,而是要访问您的基础所在的派生实例。在执行强制转换时,您需要创建一个引用或指向derived 的指针,而不是它的全新实例。以下将起作用:

derived d;
base *bp = &d;
base &br =  d;
derived &dr = static_cast<derived &>(br);
derived *dp = static_cast<derived *>(bp);

现在drdp 都指向上面相同的d

【讨论】:

将派生类的继承标记为公有,因为默认情况下是私有的,并且您呈现此代码的方式不起作用(派生类:public base int y; ;)。【参考方案3】:

原因是:它不是,它可以是指针或引用。这与以下问题有关:

 struct Base
 

 ;


 struct Derived : public Base
 
   int A;
 ;

 //sizeof(Base)==0
 //sizeof(Derived)==4
 Base B;
 Derived X;
 X.A = 10;
 B=X;//what happens to Derived::A? There is no space to put it. This is called slicing.

基本上,您不能在不冒切片风险的情况下使用派生类创建基类的实例。但是引用/指针是另一回事。在这种情况下,您只是在解释指向的内存是如何被解释的。在这种情况下,静态强制转换实际上不执行任何操作!由于 C++ 类的布局方式(有意),从 Base 类继承的所有内容都具有相同的内存布局,从偏移量 0 到 sizeof(Base)。只有在那之后你才能添加派生的东西。

【讨论】:

另外,我要补充一点,每当你必须这样做时,你已经向自己指出了哪里有糟糕的设计!【参考方案4】:

为什么 static_cast 总是需要指针?

嗯,并非总是如此。正如您所指出的,以下是可能的:

int i = static_cast<int>(3.14);

static_cast 也可以用于在引用之间进行转换,就像使用指针一样。

但是,您必须考虑以下几点:在两种类型之间进行转换时,您可能会丢失信息。假设你有一个类Animal 和另一个类Dog 恰好继承自它。这意味着什么?

Dog d;
Animal a = static_cast<Animal>(d);

您正在从Dog 创建一个Animal,但Dog 的特定信息将被丢弃。这称为切片

指针之间的转换通常只涉及重新解释内存;底层对象保持不变。

【讨论】:

是的,也可以将 static_cast 与引用一起使用。谢谢。

以上是关于为啥 static_cast 需要指针或引用?的主要内容,如果未能解决你的问题,请参考以下文章

mfc 类型间的强制转换

static_cast 剖析

为啥 gorm db.First() 会因“无效的内存地址或 nil 指针取消引用”而恐慌? [复制]

使用static_cast进行父类指针转子类指针可能出现的问题

为啥取消引用称为取消引用的指针?

为啥我可以将 static_cast void* 转换为 int* 而不能转换为 int*&?