如果函数的 return 值将用作右值引用而不是左值,是否有办法使函数具有不同的行为?
Is there a way to make a function have different behavior if its return value will be used as an rvalue reference instead of an lvalue?
我有一个执行一些中等开销操作的例程,客户端可以将结果作为字符串、整数或许多其他数据类型使用。我有一个 public 数据类型,它是内部数据类型的包装器。我的 public class 看起来像这样:
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();
如果您想要不止一种 return 类型,您可以这样做:
// Example B
Result result = Result::compute(/*...*/);
std::string resultString = result.toString();
int32_t resultInteger = result.toInteger();
一切正常。
但是,我想修改这个class,如果用户只需要一种结果类型,就不需要在堆上分配内存。例如,我希望示例 A 本质上相当于
auto result = ExpensiveInternalObject(/* args */);
std::string resultString = result.toString();
我考虑过构建代码,以便将 args 保存到 Result
的实例中,使 ExpensiveInternalObject
在终端函数 (toString
/ toInteger
/toDouble
),并使用右值引用限定符重载终端函数,如下所示:
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()
版本,一个用于Example A用例,一个用于Example B用例,但这对用户不是很友好API,因为现在他们必须研究使用哪个版本的方法,如果选择错误,他们的表现会很差。
我不能在 Result
中创建 ExpensiveInternalObject
值字段(与指针相反),因为这样做需要在 public 头文件中暴露太多内部结构。
有没有办法让第一个函数compute()
知道它的return值是要变成右值引用还是要变成左值,并且有不同的每个案例的行为?
正在考虑 you can't overload a function by its return type, and you wanted to avoid making two different versions of compute()
, the only thing I can think of is setting a flag in the copy constructor of Result
. This could work with your particular example, but not in general. For example, it won't work if you're taking a reference, which you can't disallow。
您可以使用一种代理对象实现您要求的语法。
Result::compute
可以 return 一个表示 Result
承诺的对象,而不是 Result
。这个 Promise
对象可以有一个转换运算符,隐式转换为 Result
,这样 "Example B" 仍然像以前一样工作。但是 promise 也可以有自己的 toString()
, toInteger()
, ... "Example 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,但这并不理想。
我有一个执行一些中等开销操作的例程,客户端可以将结果作为字符串、整数或许多其他数据类型使用。我有一个 public 数据类型,它是内部数据类型的包装器。我的 public class 看起来像这样:
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();
如果您想要不止一种 return 类型,您可以这样做:
// Example B
Result result = Result::compute(/*...*/);
std::string resultString = result.toString();
int32_t resultInteger = result.toInteger();
一切正常。
但是,我想修改这个class,如果用户只需要一种结果类型,就不需要在堆上分配内存。例如,我希望示例 A 本质上相当于
auto result = ExpensiveInternalObject(/* args */);
std::string resultString = result.toString();
我考虑过构建代码,以便将 args 保存到 Result
的实例中,使 ExpensiveInternalObject
在终端函数 (toString
/ toInteger
/toDouble
),并使用右值引用限定符重载终端函数,如下所示:
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()
版本,一个用于Example A用例,一个用于Example B用例,但这对用户不是很友好API,因为现在他们必须研究使用哪个版本的方法,如果选择错误,他们的表现会很差。
我不能在 Result
中创建 ExpensiveInternalObject
值字段(与指针相反),因为这样做需要在 public 头文件中暴露太多内部结构。
有没有办法让第一个函数compute()
知道它的return值是要变成右值引用还是要变成左值,并且有不同的每个案例的行为?
正在考虑 you can't overload a function by its return type, and you wanted to avoid making two different versions of compute()
, the only thing I can think of is setting a flag in the copy constructor of Result
. This could work with your particular example, but not in general. For example, it won't work if you're taking a reference, which you can't disallow。
您可以使用一种代理对象实现您要求的语法。
Result::compute
可以 return 一个表示 Result
承诺的对象,而不是 Result
。这个 Promise
对象可以有一个转换运算符,隐式转换为 Result
,这样 "Example B" 仍然像以前一样工作。但是 promise 也可以有自己的 toString()
, toInteger()
, ... "Example 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,但这并不理想。