我正在尝试编写一个函数,该函数可以返回对作为第一个参数传递的现有对象的引用(如果它处于正确状态),也可以使用作为第二个参数传递的文字创建并返回一个新对象(默认) )。
如果一个函数不仅可以使用文字,还可以使用另一个现有对象作为第二个(默认)参数并返回对其的引用,那就更好了。
下面是一个简单的实现,但它做了很多不需要的工作:
如果以lvalue作为第二个(默认)参数调用,它将调用参数的副本构造函数,该函数被选择返回。理想情况下,应返回对对象的引用。
如果使用文字作为第二个(默认)参数调用,即使未选择第二个(默认)参数返回参数,它也会调用构造函数,复制构造函数和析构函数。最好构造一个对象并将其作为右值引用返回,而不调用复制构造函数或析构函数。
std::string get_or_default(const std::string& st, const std::string& default_st) {
if (st.empty()) return default_st
else return st;
}
是否有一种方法可以更有效地完成此任务,同时仍对呼叫者保持简单?如果我是正确的话,这需要一个函数根据函数内部做出的运行时决策来更改返回类型,但是我想不到一种简单的调用方解决方案。
答案 0 :(得分:3)
我不确定我是否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;
}
答案 1 :(得分:2)
好吧,这里有几件事。
要直接表达您的要求,可以使用std::variant<std::string, std::string&>
之类的函数作为返回类型。虽然我还没有检查variant是否可以存储参考。
或第三方库中的等效项。要么<>?
您也可以编写自己的包装字符串和字符串引用的类。
(不是真实代码)
struct StringOrRef {
enum class Type {Value, Ref} type;
union {
std::string value;
std::reference_wrapper<const std::string> ref;
};
...
};
检查主题:区分C ++中的联合。
但是我认为您的示例存在更大的问题! 请考虑数据的所有权。 std :: string获取所传递数据的所有权。这就是为什么它复制数据。因此,当您的函数返回时-被调用者可以确定它具有数据,并且只要保存该值就不必担心。
如果您设计一个函数来返回对传递的参数值的引用-您需要确保在与传递的参数(返回ref的参数)寿命相同的寿命内使用该值
所以请考虑:
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&
返回对该对象的字符串字段的引用(通常是实现该对象的方式)-那么当s
超出范围时-ref无效。也就是说,它正在引用可能已提供给某些其他对象的已释放内存。
并且,如果您在val
中捕获了该引用,则val将引用无效数据。
如果全部以access violation
结尾或发出信号,您将很幸运。在最坏的情况下,您的程序会继续运行,但会随机崩溃。