包装堆栈分配的对象时,如何避免复制/内存开销



假设我有一个大的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;
}};
}

这可能太过分了。

任一:

  1. 在包装器中保留对原始对象的引用,并通过此操作使用它
  2. 使用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;
};

最新更新