有没有办法 return 一个新对象或从函数引用现有对象?

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

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

如果一个函数不仅可以接受字面量,还可以接受另一个现有对象作为第二个(默认)参数和 return 对它的引用,那就更好了。

下面是一个简单的实现,但它做了很多不需要的工作:

  1. 如果使用左值作为第二个(默认)参数调用,它会调用为 return 选择的参数的复制构造函数。理想情况下,对对象的引用应该 returned.

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

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

有没有一种方法可以更有效地完成此操作,同时仍然对调用者保持简单?如果我是正确的,这需要一个函数来根据函数内部做出的 运行 时间决定来更改 return 类型,但我想不出一个简单的调用者解决方案。

好吧,这里有一些东西。 要直接表达您的要求,您可以使用 std::variant<std::string, std::string&> 之类的东西作为您的函数 return 类型。虽然我没有检查变体是否可以存储引用。 或者来自第三方库的一些等价物。要么<>? 您还可以编写自己的 class 包装字符串和字符串引用。

(不是真实代码)

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

检查主题:C++ 中的判别联合。

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

如果您将函数设计为 return 对传递的参数值的引用 - 您需要确保该值在与传递的参数相同的生命周期内使用(引用是 returned)

所以考虑:


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& return 是对该对象的字符串字段的引用(这通常是它的实现方式) - 那么当 s 超出范围时 - 引用变得无效.也就是说 - 它正在引用可能已分配给其他一些对象的已释放内存。 如果您在 val 中捕获该引用 - val 将引用无效数据。 如果一切都以 access violation 或信号结尾,您将很幸运。在最坏的情况下,您的程序会继续运行,但会随机崩溃。

我不是 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 temporary\n";
    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 ref\n";
    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:s 测试后制作了一个稍微更通用的版本。它可以使用 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;
}