作者:琪琪 | 来源:互联网 | 2023-08-29 13:04
免责声明:这更多是出于好奇,而不是缺乏其他解决方案!
是否可以在 C++ 中实现一个函数:
- 传递一个 T 类型的指针
- 要么向 T 指向的对象返回一个类似引用的东西
- 或者,如果指针为空,则将类似引用的事物返回到
T()
具有某种合理生命周期的默认构造?
我们的第一次尝试是:
template
T& DefaultIfNullDangling(T* ptr) {
if (!ptr) {
return T(); // xxx warning C4172: returning address of local variable or temporary
} else {
return *ptr;
}
}
第二次尝试是这样完成的:
template
T& DefaultIfNull(T* ptr, T&& callSiteTemp = T()) {
if (!ptr) {
return callSiteTemp;
} else {
return *ptr;
}
}
这消除了警告并在某种程度上延长了临时的生命周期,但我认为它仍然很容易出错。
背景:
整个过程是由如下所示的访问模式触发的:
if (pThing) {
for (auto& subThing : pThing->subs1) {
// ...
if (subThing.pSubSub) {
for (auto& subSubThing : *(subThing.pSubSub)) {
// ...
}
}
}
}
可以“简化”为:
for (auto& subThing : DefaultIfNull(pThing).subs1) {
// ...
for (auto& subSubThing : DefaultIfNull(subThing.pSubSub)) {
// ...
}
}
回答
没有真正符合您要求的良好、惯用的 C++ 解决方案。
一种“EmptyIfNull”可以很好地工作的语言,可能是一种具有垃圾收集或引用计数对象的语言。因此,我们可以通过使用引用计数指针在 C++ 中实现类似的功能:
// never returns null, even if argument was null
std::shared_pr
EmptyIfNull(std::shared_pr ptr) {
return ptr
? ptr
: std::make_shared();
}
或者,您可以返回对具有静态存储持续时间的对象的引用。但是,在使用这种技术时,我不会返回可变引用,因为一个调用者可能会将对象修改为非空,这可能会使另一个调用者非常困惑:
const T&
EmptyIfNull(T* ptr) {
static T empty{};
return ptr
? *ptr
: empty;
}
或者,您仍然可以返回一个可变引用,但不修改空对象的文档是调用者必须遵守的要求。这会很脆弱,但对于 C++ 课程来说,这是标准的。
作为另一种选择,我写了一个使用类型擦除包装器的建议,它可以是引用或对象,但Ayxan Haqverdili已经涵盖了它。大量的样板文件。
一些对前提进行更多调整以适合 C++ 的替代设计:
返回一个对象:
T
EmptyIfNull(T* ptr) {
return ptr
? *ptr
: T{};
}
让调用者提供默认值:
T&
ValueOrDefault(T* ptr, T& default_) {
return ptr
? *ptr
: default_;
}
将非空参数视为前提条件:
T&
JustIndirectThrough(T* ptr) {
assert(ptr); // note that there may be better alternatives to the standard assert
return *ptr;
}
将空参数视为错误情况:
T&
JustIndirectThrough(T* ptr) {
if (!ptr) {
// note that there are alternative error handling mechanisms
throw std::invalid_argument(
"I can't deal with this :(");
}
return *ptr;
}
背景:
我认为您要求的功能对于您提供的背景不是很有吸引力。目前,如果指针为空,您什么都不做,而根据此建议,您将对空对象执行某些操作。如果您不喜欢深度嵌套的块,则可以使用以下替代方法:
if (!pThing)
continue; // or return, depending on context
for (auto& subThing : pThing->subs1) {
if (!subThing.pSubSub)
continue;
for (auto& subSubThing : *subThing.pSubSub) {
// ...
}
}
或者,也许您可以建立一个不变量,您永远不会在范围中存储 null,在这种情况下,您永远不需要检查 null。
回答
是的,但它会很丑:
#include
#include
template
struct Proxy {
private:
std::variant m_data = nullptr;
public:
Proxy(T* p) {
if (p)
m_data = p;
else
m_data = T{};
}
T* operator->() {
struct Visitor {
T* operator()(T* t) { return t; }
T* operator()(T& t) { return &t; }
};
return std::visit(Visitor{}, m_data);
}
};
struct Thing1 {
int pSubSub[3] = {};
auto begin() const { return pSubSub; }
auto end() const { return pSubSub + 3; }
};
struct Thing2 {
Thing1* subs1[3] = {};
auto begin() const { return subs1; }
auto end() const { return subs1 + 3; }
};
template
auto NullOrDefault(T* p) {
return Proxy(p);
}
int main() {
Thing1 a{1, 2, 3}, b{4, 5, 6};
Thing2 c{&a, nullptr, &b};
auto pThing = &c;
for (auto& subThing : NullOrDefault(pThing)->subs1) {
for (auto& subSubThing : NullOrDefault(subThing)->pSubSub) {
printf("%d, ", subSubThing);
}
putchar('n');
}
}