C++ 如何在C+中创建一个可以返回静态或自动存储对象而无需复制的函数+;?
假设我想写一个函数,这样:C++ 如何在C+中创建一个可以返回静态或自动存储对象而无需复制的函数+;?,c++,C++,假设我想写一个函数,这样: 它返回一个对象 在某些情况下,根据函数的参数,对象具有一个固定值,该值只能计算一次以节省时间。因此,自然的选择是使该对象成为静态对象 否则,函数必须动态生成对象 编写此函数的最佳方法是什么,要求: 调用时不需要复制构造函数,只需移动,以防止复制昂贵对象的所有数据 我不想使用new原始指针。如果需要指针,它们必须是智能的和自动删除的 用例如下所示: 某些值非常常见,因此我希望缓存它们 其他值非常罕见,因此我不希望缓存它们 调用方不应该知道哪些值是公共的,对于
- 它返回一个对象
- 在某些情况下,根据函数的参数,对象具有一个固定值,该值只能计算一次以节省时间。因此,自然的选择是使该对象成为静态对象
- 否则,函数必须动态生成对象
- 调用时不需要复制构造函数,只需移动,以防止复制昂贵对象的所有数据
- 我不想使用
原始指针。如果需要指针,它们必须是智能的和自动删除的new
- 某些值非常常见,因此我希望缓存它们
- 其他值非常罕见,因此我不希望缓存它们
shared\u ptr
,如下所示,但这感觉有些过分了。特别是,因为它进行了堆分配,它觉得实际上并不需要堆分配。有更好的方法吗
#include <cassert>
#include <iostream>
#include <memory>
struct C {
int i;
static int count;
C(int i) : i(i) {
std::cout << "constr" << std::endl;
count++;
}
C(const C& c) : C(c.i) {
std::cout << "copy" << std::endl;
}
~C() {
std::cout << "destr" << std::endl;
count--;
}
};
int C::count = 0;
std::shared_ptr<C> func_reg_maybe_static(int i) {
static auto static_obj = std::make_shared<C>(0);
if (i == 0) {
return static_obj;
} else {
return std::make_shared<C>(i);
}
}
int main() {
assert(C::count == 0);
{
auto c(func_reg_maybe_static(0));
assert(c->i == 0);
assert(C::count == 1);
}
assert(C::count == 1);
{
auto c(func_reg_maybe_static(0));
assert(c->i == 0);
assert(C::count == 1);
}
assert(C::count == 1);
{
auto c(func_reg_maybe_static(1));
assert(c->i == 1);
assert(C::count == 2);
}
assert(C::count == 1);
{
auto c(func_reg_maybe_static(2));
assert(c->i == 2);
assert(C::count == 2);
}
assert(C::count == 1);
}
并且它产生了预期的输出(我相信通过拷贝省略保证):
我添加了静态引用计数器C::count
,只是为了检查对象是否按照预期被删除
如果我没有静态案例,我会直接做:
C func_reg_maybe_static(int i) {
return C(i);
}
复制省略/移动语义将使一切变得高效
但是,如果我尝试类似的方法,如:
C func_reg_maybe_static(int i) {
static C c(0);
return c;
}
然后C++巧妙地停止移动C,并开始复制它,以避免损坏<代码>静态< /代码>。< /p> < p>我不太理解静态/自动化的目的。如果您所要做的就是避免在以前使用过参数时重复构造,那么为什么不使用缓存呢
C& func_reg(const int i)
{
static std::unordered_map<int, C> cache;
auto it = cache.find(i);
if (it == cache.end())
it = cache.emplace(i, C(i));
return it->second;
}
如果我误解了,你真的需要这个东西,传递
是静态的==true
会给你一个完全不同的对象(一个用0
而不是I
构造的对象),那么就为它创建一个新函数;它在做一些与众不同的事情
C& func_reg()
{
static C obj(0);
return obj;
}
C func_reg(const int i)
{
return C(i);
}
您需要的基本上是一个变体:
std::variant<C, C*> func_reg_maybe_static(int i)
{
static C static_obj{ 0 };
if (i == 0) {
return &static_obj;
} else {
return std::variant<C, C*>{ std::inplace_type_t<C>{}, i };
}
}
如果您没有C++17编译器,也可以对联合进行同样的操作,但是实现当然会更加复杂。然后,原始功能变为:
value_or_ptr<C> func_reg_maybe_static(int i)
{
static C static_obj{ 0 };
if (i == 0) {
return value_or_ptr{ &static_obj };
} else {
return value_or_ptr{ i };
}
}
这样做的优点是更简单,但可能需要调用方进行复制。它还要求调用方知道是否将返回预计算的对象(这是不需要的)。使用variant方法,调用方可以统一处理结果。调用者总是返回具有值语义的对象,并且不需要知道他是否会收到预先计算的对象。根据不透明条件,返回的对象可能引用先前存在的对象。这意味着函数需要通过引用返回。但是,由于未缓存值的存储必须比函数寿命长,并且不在堆上,因此调用方需要在堆栈上为返回的对象预先提供存储
C& func_reg_maybe_static(int i, void* buf)
{
static C c(0);
if(i == 0)
return c;
else
return *new (buf) C(i);
}
using uninit_C = std::aligned_storage<sizeof(C), alignof(C)>;
uninit_C buf;
auto& c = func_reg_maybe_static(i, &buf);
这个版本显然要求
C
是默认可构造的。新位置完全合法,但根据C
的内容,在函数调用后使用buf
。作用域退出时将调用析构函数。不太清楚还需要什么-如果按值返回对象,则必须移动或复制它。您可以自动移动,但必须复制静态对象。或者使用智能指针。@Slava任何比我所做的shared\u ptr
更好的方法,或者这是shared\u ptr
的有效使用吗?任何std::shared\u ptr
的使用都是有效的,除非你滥用它。我认为在这种情况下,这是最简单易懂的解决方案。@Slava您可以返回对静态本地的引用,无需共享\u ptr
@LightnessRacesinOrbit您可以返回对静态的引用,但不能返回对自动,这里似乎需要一个静态和一个自动存储。那么自动存储就没有了,对吧?@YSC:没有,但我故意放弃这个要求,因为我不太理解它的价值。从功能上讲,也就是说。OP允许动态分配,只要事情是自动清理的。从OP的最后评论来看,我完全同意+1.@CiroSantilli新疆改造中心六四事件法轮功 也许,但现在我们真的在我的领域里,不明白你想解决什么问题,对不起。你让我意识到我没有把我的问题很好地映射到这个问题上。我对问题进行了编辑,希望这是本例中的正确方法,而不是问一个新方法。这种过于复杂的方法比使用std::shared\u ptr
更好,这一点非常明显?这避免了不必要的堆分配。或者只是两个单独的函数,一个返回C&
,另一个返回C
!我认为根本不需要共享\u ptr
。我一定是遗漏了什么。这可以用更简单的方法来完成-std::shared\u ptr
指向静态对象(如果真的有必要的话)。我怀疑这是真的。我已经更新了这个问题,以防止使用不同的接口作弊:-)很抱歉搞砸了。为什么我们需要return*new(buf)C(I)
,而不仅仅是在else语句中返回C(i)
?@sahildhoke我们通过引用返回。酷,我不知道新的位置。您能否澄清“新的位置完全合法,但在函数调用后使用buf可能合法,也可能不合法,这取决于C的内容。”:现在什么时候合法?@CiroSantilli新疆改造中心六四事件法轮功 添加了链接。@Ci
std::variant<C, C*> func_reg_maybe_static(int i)
{
static C static_obj{ 0 };
if (i == 0) {
return &static_obj;
} else {
return std::variant<C, C*>{ std::inplace_type_t<C>{}, i };
}
}
template <class C> class value_or_ptr
{
std::variant<C, C*> object_;
public:
explicit template <class... Params> value_or_ptr(Params&&... parameters)
: object_(std::inplace_type_t<C>{}, std::forward<Params>(parameters)...)
{}
explicit value_or_ptr(C* object)
: object_(object)
{}
// other constructors...
C& operator*()
{
return object_.index() == 0 ? std::get<0>(object_) : *std::get<1>(object_);
}
// other accessors...
}
value_or_ptr<C> func_reg_maybe_static(int i)
{
static C static_obj{ 0 };
if (i == 0) {
return value_or_ptr{ &static_obj };
} else {
return value_or_ptr{ i };
}
}
C& func_reg()
{
static C obj{ 0 };
return obj;
}
C func_reg(const int i)
{
return C{ i };
}
C& func_reg_maybe_static(int i, void* buf)
{
static C c(0);
if(i == 0)
return c;
else
return *new (buf) C(i);
}
using uninit_C = std::aligned_storage<sizeof(C), alignof(C)>;
uninit_C buf;
auto& c = func_reg_maybe_static(i, &buf);
C& func_reg_maybe_static(int i, C& buf)
{
static C c(0);
if(i == 0)
return c;
else
{
buf.~C();
return *new (&buf) C(i); // note below
}
}
C buf;
auto& c = func_reg_maybe_static(i, buf);