我有一个执行一些中等昂贵操作的例程,客户端可以将结果用作字符串,整数或许多其他数据类型。我有一个公共数据类型,它是内部数据类型的包装器。我的公共课看起来像这样:
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>
答案 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;
}
};
// ...
};
这种方法有其缺点。例如,如果你写了:
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。