有没有一种方法可以从函数中返回一个新对象或对现有对象的引用

Is there a way to return either a new object or reference to existing object from a function?

本文关键字:对象 新对象 引用 一个 方法 一种 返回 有没有 函数      更新时间:2023-10-16

我正在尝试编写一个函数,它可以返回对作为第一个参数传递的现有对象的引用(如果它处于正确状态(,也可以使用作为第二个参数传递来的文字创建并返回新对象(默认值(。

如果一个函数不仅可以接受文本,还可以接受另一个现有对象作为第二个(默认(参数,并返回对它的引用,那就更好了

下面是一个琐碎的实现,但它做了很多不必要的工作:

  1. 如果使用lvalue作为第二个(默认(参数进行调用,则会调用参数的复制构造函数,该构造函数被选择用于返回。理想情况下,应该返回对对象的引用。

  2. 如果使用literal作为第二个(默认(参数进行调用,它将调用构造函数、复制构造函数和析构函数,即使没有选择第二个参数(默认(进行返回。如果在不调用复制构造函数或析构函数的情况下构造对象并将其作为右值引用返回,效果会更好。

std::string get_or_default(const std::string& st, const std::string& default_st) {
if (st.empty()) return default_st
else return st;
}

有没有一种方法可以更有效地实现这一点,同时对调用者保持简单?如果我是正确的,这需要函数根据函数内部的运行时决定来更改返回类型,但我想不出一个简单的调用方解决方案。

我不能100%确定我理解需求的组合,但:

#include <iostream>
#include <string>
#include <type_traits>
// if called with an rvalue (xvalue) as 2:nd arg, move or copy
std::string get_or_default(const std::string& st, std::string&& default_st) {
std::cout << "got temporaryn";
if(st.empty())
return std::move(default_st);      // rval, move ctor
// return std::forward<std::string>(default_st); // alternative
else
return st;                         // lval, copy ctor
}
// lvalue as 2:nd argument, return the reference as-is
const std::string& get_or_default(const std::string& st,
const std::string& default_st) {
std::cout << "got refn";
if(st.empty()) return default_st;
else           return st;
}
int main() {
std::string lval = "lval";
// get ref or copy ...
decltype(auto) s1 = get_or_default("", "temporary1");
decltype(auto) s2 = get_or_default("", std::string("temporary2"));
decltype(auto) s3 = get_or_default("", lval);
std::cout << std::boolalpha;
std::cout << std::is_reference_v<decltype(s1)> << "n";
std::cout << std::is_reference_v<decltype(s2)> << "n";
std::cout << std::is_reference_v<decltype(s3)> << "n";
}

输出:

got temporary
got temporary
got ref
false
false
true

编辑:在OP测试后,制作了一个稍微更通用的版本。它可以使用lambda,如
auto empty_check = [](const std::string& s) { return s.empty(); };
来测试第一个参数是否为空。

template<typename T, typename F>
T get_or_default(const T& st, T&& default_st, F empty) {
if(empty(st)) return std::move(default_st);
// return std::forward<T>(default_st); // alternative
else          return st;
}
template<typename T, typename F>
const T& get_or_default(const T& st, const T& default_st, F empty) {
if(empty(st)) return default_st;
else          return st;
}

好吧,这里有一些东西。要直接表达您的要求,可以使用std::variant<std::string, std::string&>之类的函数返回类型。尽管我还没有检查变体是否可以存储引用。或者来自第三方图书馆的类似资料。或者<>?您也可以编写自己的类包装字符串和字符串参考

(不是真正的代码(

struct StringOrRef {
enum class Type {Value, Ref} type;
union {
std::string value;
std::reference_wrapper<const std::string> ref;
};
...
};

检查主题:在C++中辨别并集。

但我认为你的例子还有更大的问题!请考虑数据的所有权。std::string获取所传递数据的所有权。这就是它复制数据的原因。因此,当函数返回时,被调用方确信它有一个数据,只要他持有该值,就不需要担心它。

如果您设计了一个函数来返回对传递的参数值的引用,则需要确保该值在与传递的参数(返回引用的参数(相同的寿命内使用

所以考虑一下:


StringOrRef func(strging const& a, string const& b);
...
StringOrRef val;
{ // begin scope:

SomeStruct s = get_defaul();
val = func("some value", s.get_ref_to_internal_string());
}// end of val scope
val; // is in scope but may be referencing freed data. 

这里的问题是临时对象SomeStruct s。如果它的成员函数get_ref_to_internal_string() -> string&向该对象的字符串字段返回一个ref(这通常是它的实现方式(,那么当s超出范围时,该ref将变为无效。也就是说,它引用的是释放的内存,这些内存可能已经被赋予了其他一些对象。如果您在val中捕获该引用,那么val将引用无效数据。如果这一切都以access violation或一个信号结束,你将是幸运的。最坏的情况是,你的程序会继续运行,但会随机崩溃。