如果函数的返回值将用作右值引用而不是左值,是否有办法使函数具有不同的行为?

时间:2018-04-07 04:29:09

标签: c++ api-design rvalue-reference

我有一个执行一些中等昂贵操作的例程,客户端可以将结果用作字符串,整数或许多其他数据类型。我有一个公共数据类型,它是内部数据类型的包装器。我的公共课看起来像这样:

class Result {
 public:
  static Result compute(/* args */) {
    Result result;
    result.fData = new ExpensiveInternalObject(/* args */);
    return result;
  }

  // ... constructors, destructor, assignment operators ...

  std::string toString() const { return fData->toString(); }
  int32_t toInteger() const { return fData->toInteger(); }
  double toDouble() const { return fData->toDouble(); }

 private:
  ExpensiveInternalObject* fData;
}

如果你想要字符串,你可以这样使用它:

// Example A
std::string resultString = Result::compute(/*...*/).toString();

如果您想要多种返回类型,请按以下方式执行:

// Example B
Result result = Result::compute(/*...*/);
std::string resultString = result.toString();
int32_t resultInteger = result.toInteger();

一切正常。

然而,我想修改这个类,这样如果用户只需要一个结果类型,就不需要在堆上分配内存。例如,我希望示例A基本上相当于,

auto result = ExpensiveInternalObject(/* args */);
std::string resultString = result.toString();

我已经考虑过构造代码,以便将args保存到Result的实例中,直到终端函数(ExpensiveInternalObject /)才能计算toString toInteger / toDouble),并使用rvalue引用限定符重载终端函数,如下所示:

class Result {
  // ...
  std::string toString() const & {
    if (fData == nullptr) {
      const_cast<Result*>(this)->fData = new ExpensiveInternalObject(/*...*/);
    }
    return fData->toString();
  }
  std::string toString() && {
    auto result = ExpensiveInternalObject(/*...*/);
    return result.toString();
  }
  // ...
}

虽然这可以避免示例A调用站点的堆分配,但这种方法的问题是您必须开始考虑线程安全问题。您可能希望fData成为std::atomic,这会增加示例B调用网站的开销。

另一种选择是以不同的名称制作两个版本的compute(),一个用于示例A用例,一个用于示例B用例,但这对用户来说并不是非常友好。 API,因为现在他们必须研究使用哪种方法,如果他们选择了错误的方法,他们的表现会很差。

我无法在ExpensiveInternalObject内使Result成为值字段(而不是指针),因为这样做需要在公共头文件中暴露太多内部。

有没有办法让第一个函数compute()知道它的返回值是否会成为右值引用,或者它是否会成为左值,并且每种情况都有不同的行为?< / p>

2 个答案:

答案 0 :(得分:1)

您可以使用一种代理对象来实现所要求的语法。

Result可以返回代表Result::compute的承诺的对象,而不是Result。此Promise对象可以有一个转换运算符,隐式转换为Result,以便“示例B”仍然像以前一样工作。但是承诺也可能有自己的toString()toInteger(),......成员函数用于“示例A”:

class Result {
 public:
   class Promise {
     private:
      // args

     public:
     std::string toString() const { 
        auto result = ExpensiveInternalObject(/* args */);
        return result.toString(); 
     } 
     operator Result() { 
        Result result;
        result.fData = new ExpensiveInternalObject(/* args */);
        return result;
     }
   };

   // ...

};

Live demo

这种方法有其缺点。例如,如果你写了:

 auto result = Result::compute(/*...*/);
 std::string resultString = result.toString();
 int32_t resultInteger = result.toInteger();

result现在不属于Result类型,但实际上是Result::Promise,您最终计算ExpensiveInternalObject两次!您可以通过向toString()上的toInteger()Result::Promise,...成员函数添加右值参考限定符,至少将其设为fail to compile,但这并不理想。< / p>

答案 1 :(得分:0)

通过返回类型考虑you can't overload函数,并且您希望避免生成compute()的两个不同版本,我唯一能想到的是在{{的复制构造函数中设置一个标志1}}。这可能适用于您的特定示例,但不是一般的。例如,如果您正在参考you can't disallow

,它将无效