转换后的引用是否安全返回?

15

Point 是一个类,其实例可以明确地强制转换为 wxPoint

class Point{
private:
    int x;
    int y;
public:
    explicit operator wxPoint() const
    {
        return wxPoint(x, y);
    }

    // More stuff
}

我有一个返回 Point 类型对象引用的函数。该函数的头文件如下:

const Point& GetPoint();

我的问题是:定义以下 Foo 函数是否安全?

const wxPoint& Foo() const{
    return (wxPoint&) GetPoint();
}

我最初使用 return (wxPoint) GetPoint(); 实现了 Foo,但是这样会创建一个新的(本地)对象,因此触发了有用的警告。在此处显示的代码可编译且没有警告。

我找到的有关这种类型转换的所有信息都是关于继承类的,但这里并非如此。

解释以这种方式转换引用时发生的确切情况将非常感激。


定义那个函数是完全“安全”的。有一些使用该函数的方式可能会导致问题,而有些则不会。 - Pete Becker
无论如何,如果你想写不可维护的代码...(调用返回对象上的任何成员函数都会引发未定义行为)。 - rustyx
2
@PeteBecker 为什么你说这是完全“安全”的呢?在我看来,这是完全不安全的。 - MRB
6
这就是为什么应该选择 C++ 式转换的原因。如果你使用了它,编译器会更加严格地保护你。C 风格的引用转换会忽略类型系统提供的任何保护,你最终可能会返回一个悬空引用。 - StoryTeller - Unslander Monica
@MRB - 我的观点表达得不是特别清楚。定义这样一个函数不会造成任何可能的伤害。但是,使用它可能会有危害。因此,它是否“安全”(无论这意味着什么)取决于其预期用途,并且问题中没有足够的信息来评估它。 - Pete Becker
从两个函数中都通过值返回,而不是返回引用或将引用强制转换为不相关的类型。返回引用不安全,将引用强制转换为不相关的类型也不安全。 - n. m.
3个回答

16

实际上你的转换运算符从未被调用。你正在从GetPoint中返回一个指向point实例的引用。稍后,您使用了C风格的强制类型转换,在您的情况下,将等同于reinterpret_cast<>(请参见此处)。您正在将指向Point的引用转换为指向wxPoint的引用,而这两个引用类型完全不相关。另一方面,对返回的引用执行任何操作都是未定义的行为。

我的建议是始终使用C ++强制转换运算符。它们有以下好处:

  • C++风格的强制转换由编译器检查。
  • C++风格的强制转换可以轻松搜索。
  • C++风格的强制转换表达了程序员的意图。

7
@PeterA.Schneider 这将调用转换运算符以构造一个 wxPoint 临时对象,但随后会尝试将 wxPoint& 绑定到该临时对象,这是不合法的。问题在于函数的返回类型:如果没有任何东西可以绑定引用,则无法返回引用。 - Jonathan Wakely
2
没错。这不是标准的问题,而是GCC过于热心了。考虑一下这个例子。clang的警告表明了发生了什么。你需要使用static_cast来正确调整指针,但你不能这样做,因为基类是不可访问的。只有C风格的转换才能正确地完成工作,并且没有警告。 - StoryTeller - Unslander Monica
3
请看这里。注意中间段落:"可以使用显式类型转换的强制转换符号来执行此操作。相同的语义限制和行为也适用,但在以下情况下执行static_­cast时,即使基类不可访问,转换也是有效的。" - StoryTeller - Unslander Monica
2
@StoryTeller 哇,我一直认为 C 风格的强制类型转换和 C++ 的强制类型转换是一样的,只是按照递增顺序执行,直到成功。 - Peter - Reinstate Monica
5
C++的另一个优点是它的转换语法看起来很丑陋。转换应该是丑陋的,因为虽然它们经常很有用,但它们总是需要额外的审查。 - Muzer
显示剩余6条评论

13
其他答案已经解释了将Point&强制转换为wxPoint&只是欺骗编译器,告诉它引用绑定的对象是一个wxPoint对象,这是不正确的。如果您尝试通过该引用访问wxPoint对象,则程序的行为将是未定义的,因为它没有绑定到wxPoint对象。有时候,当您欺骗编译器时,它无法给您警告,并且只能相信您不会做出疯狂的事情。

修复方法是停止尝试返回指向不存在的对象的引用:

wxPoint Foo() const {
    return GetPoint();
}

这将使用您的转换运算符构造一个wxPoint,并按值返回它,这是可以的。当没有任何东西与引用绑定时,尝试通过引用返回是不可行的。


很遗憾,我还有一个限制:Foo()头文件不能被修改 :( - Alejandro

4
将一个引用转化为不相关的引用类型是不安全的。虽然强制转换本身没有副作用,但是通过另一种类型的引用访问原始对象具有未定义的行为(除非类型别名规则提供异常,比如它们在char的情况下做到了,但这不适用于这里,因为标准没有为wxPoint指定这样的异常)。转换运算符不参与其中也没有任何区别。
一个解释在进行这种引用转换时发生了什么将会非常受欢迎。 "C风格"的转换按照优先顺序执行const_cast、static_cast或reinterpret_cast或它们的组合。没有适用于不相关引用的static_cast。唯一适用于将const Point&转换为wxPoint&的方法是reinterpret_cast后跟const_cast。reinterpret_cast执行以下操作:6)类型为T1的lvalue表达式可以转换为另一种类型T2的引用。结果是一个引用,指向与原始lvalue相同的对象,但具有不同的类型。不创建临时副本,不调用构造函数或转换函数。如果允许类型别名规则(见下文),则只能安全地访问生成的引用。

这里展示的代码编译时没有警告。

使用reinterpret_cast就像告诉编译器你知道自己在干什么,可能看起来有点疯狂,但不用担心,你知道自己在做什么,所以不需要警告。使用它应该非常小心。

由于C风格转换可能会隐式地执行reinterpret_cast,因此对它的谨慎使用也同样适用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接