const_cast 和 std::move 从非引用中删除常量
const_cast and std::move to remove constness from non-reference
我有一个无法修改的外部库。该库声明了一个模板函数,由于某种原因 returns const
非引用对象:
template<class C>
const C foo();
我还有另一个外部库,我也无法修改。该库声明了一个不可复制的 class,并且仅具有来自非常量对象的移动构造函数:
struct bar {
bar();
bar(const bar&)=delete;
bar(bar&&);
};
现在我需要使用foo<bar>
。一个简单的用法:
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>()));
}
它编译并且整个代码按预期工作(不仅是上面的简化示例,还有我的真实代码)。
但是,它是安全的,还是我 运行 陷入了某种未定义的行为?有没有更好的解决方法?
我已阅读并理解有关从函数 (1, 2) 返回 const
的问题,常见的答案似乎是现代 C++ 不鼓励返回 const
对象,但我的问题不是关于它,而是关于如何解决外部库 returns const
对象时的情况。
从技术上讲,您将程序暴露给未定义的行为。由于原始对象 C
(一个临时对象)被声明为 const
,const 转换和修改它是非法的并且违反了标准。 (我假设,移动构造函数对 movee 做了一些修改)。
话虽这么说,它可能适用于您的环境,但我没有看到更好的解决方法。
根据定义,函数调用的结果本身就是一个 R 值,因此您无需在 return 语句中对其应用 std::move
- const_cast<bar&&>(foo<bar>())
就足够了。这使代码更易于阅读。
仍然 - 没有标准保证这将始终适用于所有 bar
类型。甚至更多 - 这在某些情况下可能会导致未定义的行为。(想象一些非常侵入性的优化,它完全根除 foo
并使其结果成为 "static data" 内存段中的对象 - 就像 foo
是一个 constexpr
。然后调用可能会修改其参数的移动构造函数,可能会导致访问冲突异常)。
你所能做的就是切换到不同的库(或者,如果可能的话,请库维护者修复 API)或者创建一些单元测试并将其包含在你的构建过程中——只要测试通过,你应该没问题(记住使用与 "production" 构建中相同的优化设置 - const_cast
是其中一个强烈依赖于编译设置的东西)。
如果 bar
的移动构造函数修改了任何内容,则放弃 const 将导致未定义的行为。你可以像这样解决你的问题而不引入未定义的行为:
struct wrapped_bar {
mutable bar wrapped;
};
bar buz()
{
return foo<wrapped_bar>().wrapped;
}
使 wrapped
成员可变意味着该成员是非常量,即使 wrapped_bar
对象作为一个整体是常量。根据 foo()
的工作方式,您可能需要向 wrapped_bar
添加成员以使其更像 bar
。
我有一个无法修改的外部库。该库声明了一个模板函数,由于某种原因 returns const
非引用对象:
template<class C>
const C foo();
我还有另一个外部库,我也无法修改。该库声明了一个不可复制的 class,并且仅具有来自非常量对象的移动构造函数:
struct bar {
bar();
bar(const bar&)=delete;
bar(bar&&);
};
现在我需要使用foo<bar>
。一个简单的用法:
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>()));
}
它编译并且整个代码按预期工作(不仅是上面的简化示例,还有我的真实代码)。
但是,它是安全的,还是我 运行 陷入了某种未定义的行为?有没有更好的解决方法?
我已阅读并理解有关从函数 (1, 2) 返回 const
的问题,常见的答案似乎是现代 C++ 不鼓励返回 const
对象,但我的问题不是关于它,而是关于如何解决外部库 returns const
对象时的情况。
从技术上讲,您将程序暴露给未定义的行为。由于原始对象 C
(一个临时对象)被声明为 const
,const 转换和修改它是非法的并且违反了标准。 (我假设,移动构造函数对 movee 做了一些修改)。
话虽这么说,它可能适用于您的环境,但我没有看到更好的解决方法。
根据定义,函数调用的结果本身就是一个 R 值,因此您无需在 return 语句中对其应用 std::move
- const_cast<bar&&>(foo<bar>())
就足够了。这使代码更易于阅读。
仍然 - 没有标准保证这将始终适用于所有 bar
类型。甚至更多 - 这在某些情况下可能会导致未定义的行为。(想象一些非常侵入性的优化,它完全根除 foo
并使其结果成为 "static data" 内存段中的对象 - 就像 foo
是一个 constexpr
。然后调用可能会修改其参数的移动构造函数,可能会导致访问冲突异常)。
你所能做的就是切换到不同的库(或者,如果可能的话,请库维护者修复 API)或者创建一些单元测试并将其包含在你的构建过程中——只要测试通过,你应该没问题(记住使用与 "production" 构建中相同的优化设置 - const_cast
是其中一个强烈依赖于编译设置的东西)。
如果 bar
的移动构造函数修改了任何内容,则放弃 const 将导致未定义的行为。你可以像这样解决你的问题而不引入未定义的行为:
struct wrapped_bar {
mutable bar wrapped;
};
bar buz()
{
return foo<wrapped_bar>().wrapped;
}
使 wrapped
成员可变意味着该成员是非常量,即使 wrapped_bar
对象作为一个整体是常量。根据 foo()
的工作方式,您可能需要向 wrapped_bar
添加成员以使其更像 bar
。