【问题标题】:const_cast and std::move to remove constness from non-referenceconst_cast 和 std::move 从非引用中删除常量
【发布时间】:2016-09-21 22:49:42
【问题描述】:

我有一个无法修改的外部库。该库声明了一个模板函数,由于某种原因返回 const 非引用对象:

template<class C>
const C foo();

我还有另一个无法修改的外部库。该库声明了一个不可复制的类,并且仅具有来自非常量对象的移动构造函数:

struct bar {
    bar();
    bar(const bar&)=delete;
    bar(bar&&);
};

现在我需要使用foo&lt;bar&gt;。一个简单的用法:

bar buz() {
    return foo<bar>();
}

失败了

main.cpp: In function 'bar buz()':
main.cpp:13:21: error: use of deleted function 'bar::bar(const bar&)'
     return foo<bar>();
                     ^
main.cpp:8:5: note: declared here
     bar(const bar&)=delete;
     ^~~

这是有道理的,没有简单的解决方法可以编译代码。

但是,如果我添加一些更复杂的解决方法:

bar buz() {
    return const_cast<bar&&>(std::move(foo<bar>()));
}

它编译并且整个代码按预期工作(不仅是上面的简化示例,还有我的真实代码)。

但是,它是安全的,还是我遇到了一些未定义的行为?有没有更好的解决方法?


我已阅读并了解有关从函数(12)返回 const 的问题,常见的答案似乎是在现代 C++ 中不鼓励返回 const 对象,但我的问题不是关于它,而是关于当外部库返回const 对象时如何解决这种情况。

【问题讨论】:

  • 问题中隐含的假设是 API 在这里是错误的。你正在使用什么库?您要解决哪些问题需要您围绕 API 工作?
  • const_cast&lt;bar&gt;(foo&lt;bar&gt;()) 还不够吗?它仍然在返回语句中留下未命名的对象,所以移动语义应该启动
  • @Petr :/ 可悲的是,我会尝试 const_cast&lt;bar&amp;&amp;&gt; 而不使用 std::move,只是为了让它更简单。另外-您确定 foo 内部没有任何特定内容可以使const 合理吗?
  • 不要依赖未定义的行为。如果需要,切换库
  • @Petr 好吧,我更喜欢保证工作的长代码,而不是将来可能会中断的短代码:)

标签: c++ c++11 constants move-semantics


【解决方案1】:

如果bar 的移动构造函数修改了任何内容,则丢弃 const 将导致未定义的行为。您可能可以像这样解决您的问题而不会引入未定义的行为:

struct wrapped_bar {
    mutable bar wrapped;
};

bar buz()
{
    return foo<wrapped_bar>().wrapped;
}

wrapped 成员是可变的意味着该成员是非常量的,即使wrapped_bar 对象作为一个整体是常量。根据foo() 的工作方式,您可能需要将成员添加到wrapped_bar 以使其更像bar

【讨论】:

  • +1 好主意!鉴于wrapped_barbuz() 的实现细节,最好在buz() 的主体中定义它。当然,这一切都依赖于foo&lt;T&gt; 为任何T 工作。
【解决方案2】:

从技术上讲,您将程序暴露给未定义的行为。由于原始对象C(临时)被声明为const,因此对其进行常量转换和修改是非法的并且违反标准。 (我假设,move构造函数对movee做了一些修改)。

话虽如此,它可能适用于您的环境,但我没有看到更好的解决方法。

【讨论】:

  • @DavidHaim,首先,它创建为const。返回的临时对象是一个单独的对象,函数签名要求它是 const。比,可能是基于 OP 自己的声明(并且整个代码按预期工作)。
  • 纯属猜测。说它“可能是未定义的行为”是这里唯一确定的答案,因为 OP 只关心 about casting const away
  • @sleeptightpupper,你能详细说明我在这里推测的时间点吗?
  • 我不会 const 强制转换导致 UB,事实是如果你修改声明为 const 的实际对象。
  • @0x499602D2,这确实是真的 - 但是,我用在那里使用的代码回答了具体问题(很可能,右值 ctor 做了一些修改)。为了清楚起见,我仍然会进行编辑。
【解决方案3】:

由于函数调用的结果本身就是一个 R 值,因此您不需要在 return 语句中对其应用 std::move - const_cast&lt;bar&amp;&amp;&gt;(foo&lt;bar&gt;()) 就足够了。这使代码更易于阅读。

仍然 - 没有标准保证这将始终适用于所有 bar 类型。甚至更多——在某些情况下这可能会导致未定义的行为。(想象一下一些非常侵入性的优化,它完全消除了foo 并使其结果成为内存“静态数据”段中的对象——就像foo 是@987654326 @. 然后调用移动构造函数,这可能会修改其参数,可能会导致访问冲突异常)。

您所能做的就是切换到不同的库(或者,如果可能,请库维护者修复 API)或创建一些单元测试并将其包含在您的构建过程中 - 只要测试通过,您应该没问题(请记住使用与“生产”构建中相同的优化设置 - const_cast 是其中之一,很大程度上取决于编译设置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    • 2016-06-25
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    相关资源
    最近更新 更多