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);