假设我有一个大的blob对象,它是堆栈分配的。我需要把它放在包装器对象中,但我想避免复制。我应该将std::move
与move构造函数一起使用吗?证明它有效的最简单方法是什么?
struct Blob {
char blob[1024 * 1024]; // imagine something big here
};
template <typename T>
struct Foo {
Foo(T&& src) : data{src} {}
T data;
};
int main() {
Blob blob;
Foo foo{std::move(blob)}; // do not copy
// should not take twice the memory of a blob
}
在这种情况下,Blob
是普通的旧数据。编译器可以自由地优化不存在的变量Blob blob
。
它也可以免费制作一百万份,没有任何理由。标准并没有限制它。
存在一种称为"优化"的优化;静态单一指派";它将局部对象状态表示为独立的存在变量,这允许编译器消除一堆无意义的副本或其他无关紧要的状态更改。足够复杂的代码,或者引用/指针在优化范围之外泄漏,阻止它。
因此,在实践中,不要阻止编译器优化Blob blob
的存在。
话虽如此,
Foo(T&& src) : data{src} {}
这个应该是
Foo(T&& src) : data{std::forward<T>(src)} {}
此外,你可以做
Foo<Blob&> foo(blob);
并且使CCD_ 5存储对CCD_ 6数据的引用。这改变了";值语义";然而,CCD_ 7以令人讨厌的方式。
请注意,您的隐式推导指南同样疯狂,因为如果您传递一个左值,您将得到一个包裹引用的Foo
,但如果您传递右值,您会得到包裹值的Foo
。
如果你想要一个硬保证,C++不提供。C++甚至不能保证Blob blob;
实际占用堆栈上的空间。
更进一步,你可以这样做:
int main() {
Foo foo{[&]{
Blob blob;
return blob;
}()};
}
在这种情况下,Blob blob
的存在被忽略到返回值中,返回值又被传递给foo
,然后返回值的生存期在完整表达式的末尾结束。您也可以使用main
之外的另一个函数来代替lambda。
这使得编译器更容易发现在单独的Blob
对象中没有意义,但数量不会很大。
如果您想使Blob blob
直接省略到Foo data
中更有保障,请添加一个采用Blob
工厂的Foo
构造函数:
template <typename T>
struct Foo {
template<class U>
requires std::is_same_v< std::decay_t<U>, T >
Foo(U&& src) : data{std::forward<U>(src)} {}
template<class F>
requires std::is_invocable_r_v< T, F&& >
Foo(F&& f) : data(std::forward<F>(f)()) {}
T data;
};
template<class T>
requires !std::is_invocable_v< T&& >
Foo(T&&)->Foo<std::decay_t<T>>;
template<class F>
requires std::is_invocable_v< F&& >
Foo(F&&)->Foo<std::decay_t<std::invoke_result_t<F&&>>>;
int main() {
Foo foo{[&]{
Blob blob;
return blob;
}};
}
这可能太过分了。
任一:
- 在包装器中保留对原始对象的引用,并通过此操作使用它
- 使用NRVO以避免复制。例如
struct Blob {
char blob[1024 * 1024]; // imagine something big here
};
template <typename T>
struct Foo {
template<typename = std::enable_if_t<std::is_default_constructible<T>::value>>
Foo(): data(){}
T data;
};
Foo<Blob> ProcessBlob(){
Foo<Blob> value{};
Blob& blob = value.data;
// Use `blob` here
return value; // NRVO would eliminate copying
}
void ProcessIn2Steps(){
Foo<Blob> wrapped = ProcessBlob();
// Use `wrapped` here
}
此外,如果您真的想避免复制,可以考虑删除Foo
:的复制构造函数
template <typename T>
struct Foo {
template<typename = std::enable_if_t<std::is_default_constructible<T>::value>>
Foo(): data(){}
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
Foo& operator=(const Foo&) = delete;
Foo& operator=(Foo&&) = delete;
T data;
};