c++ - Move only type adapting std::any with dummy copy constructor is safe?
问题描述
I'd like to initialize std::any
with a move only type variable. I found Cannot move std::any.
Compile error case
Before I use shared_ptr workaround from the linked answer, I tested the following code:
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) = delete;
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
int main() {
move_only m;
std::any a(std::move(m)); // error. copy constructor is required
}
https://wandbox.org/permlink/h6HOSdgOnQYg4a6K
The code above outputs compile error because of move_only
doesn't have copy constructor.
Add copy constructor for test
I added copy constructor for test.
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) {
// not called
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
int main() {
move_only m;
std::any a(std::move(m)); // success but copy constructor is not called
}
https://wandbox.org/permlink/kxEnIslmVnJNRSn6
Then compile successfully finished as I expected. And I got interesting output.
move_only::move_only()
move_only::move_only(move_only &&)
It seems that the copy constructor isn't called. It is surprising for me.
And I come up with the following wrapper approach.
Add dummy copy constructor wrapper
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) = delete;
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
struct wrapped_move_only : move_only {
wrapped_move_only(move_only&& m):move_only(std::move(m)) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
wrapped_move_only(wrapped_move_only const&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
assert(false);
}
wrapped_move_only(wrapped_move_only &&) = default;
};
int main() {
move_only m;
wrapped_move_only wmo(std::move(m));
std::any a(std::move(wmo));
}
https://wandbox.org/permlink/EDhq3KPWKP9fCA9v
The copy constructor of move_only is deleted. The class wapped_move_only inherits move_only and added copy constructor.
It successfully compiled and I got the following result.
move_only::move_only()
move_only::move_only(move_only &&)
wrapped_move_only::wrapped_move_only(move_only &&)
move_only::move_only(move_only &&)
It seems that I initialized std::any with move only type using the wrapper that provides dummy copy constructor. It is more efficient to use shared_ptr if the goal is just initialize std::any with move only type. It is expected behavior for me.
As long as I do move operation only for std::any
once move_only
is moved to std::any
, is this code safe?
If std::any
is copied, then assetion failed due to copy constructor of wrapped_move_only is called. I want to know move only case's safety.
I am also not sure why std::any
's target requires copy constructor but it is not called.
templatized
If it is safe, I can improve this approach using template. The template add_dummy_copy_constructor
is a kind of adaptor.
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) = delete;
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
template <typename T>
struct add_dummy_copy_constructor : T {
add_dummy_copy_constructor(T&& t):T(std::move(t)) {}
add_dummy_copy_constructor(add_dummy_copy_constructor const&) {
assert(false);
}
add_dummy_copy_constructor(add_dummy_copy_constructor &&) = default;
};
int main() {
move_only m;
std::any a(add_dummy_copy_constructor(std::move(m)));
}
解决方案
I am also not sure why
std::any
's target requires copy constructor but it is not called.
The design of std::any
is to be one concrete type that can hold any copyable type. When you copy a std::any
, you copy whatever it is it's holding underneath
The std::any
needs to know how to copy the underlying object regardless of if it is ever actually going to be copied (how would it know whether or not this happens?). So it must be a compile error if the underlying type isn't copy-constructible.
However, when we're constructing the std::any
itself, at that point we know the concrete object we're constructing from. And if that concrete object happens to be an rvalue, then we can just move-construct std::any
's underlying object from the constructor parameter rather than copy-constructing. It's a free win there.
None of your code actually copies a std::any
, so none of it would invoke std::any
's copy constructor which would invoke the underlying types copy constructor.
A similar thing happens with std::function
and maybe it would be more obvious what the difference here is. When I construct a std::function<void()>
, there is the static requirement that the object be invokable with no arguments. It is a compile error if it is not invokabe.
But simply constructing the std::function<void()>
will not actually invoke the underlying function - those are separate operations. You wouldn't expect this assertion to trigger:
std::function<void()> f = []{ assert(false); }
推荐阅读
- c# - 在身份验证失败时返回自定义状态码 .Net Core
- android - 带 Hilt 的动态功能模块
- cakephp - 从不同的控制器访问公共函数内的变量
- graphql - 如何使用 graphql-constraint-directive 在 graphQL 模式中应用验证
- react-native - 在 StackNavigator 中更改特定屏幕的动画方向?
- typescript - Prisma 迁移需要默认值吗?
- android - 我可以在振铃流上播放颤振的声音吗?
- python - FileNotFoundError:没有这样的文件或目录。(Python_pygame)
- google-sheets - 使用 COUNTIF 排除同一列中的重复项
- shell - 在输入 123 到下面的脚本后,我期望回显某些东西,但我什么也没得到