是否可以在C++中实现DefaultIfNull函数?

免责声明:这更多是出于好奇,而不是缺乏其他解决方案!

是否可以在 C++ 中实现一个函数

  • 传递一个 T 类型的指针
  • 要么向 T 指向的对象返回一个类似引用的东西
  • 或者,如果指针为空,则将类似引用的事物返回到T() 具有某种合理生命周期的默认构造?

我们的第一次尝试是:

template<typename T>
T& DefaultIfNullDangling(T* ptr) {
    if (!ptr) {
        return T(); // xxx warning C4172: returning address of local variable or temporary
    } else {
        return *ptr;
    }
}

第二次尝试是这样完成的:

template<typename T>
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<T>
EmptyIfNull(std::shared_pr<T> ptr) {
    return ptr
        ? ptr
        : std::make_shared<T>();
}

或者,您可以返回对具有静态存储持续时间的对象的引用。但是,在使用这种技术时,我不会返回可变引用,因为一个调用者可能会将对象修改为非空,这可能会使另一个调用者非常困惑:

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 <stdio.h>

#include <variant>

template <class T>
struct Proxy {
 private:
  std::variant<T*, T> 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 <class T>
auto NullOrDefault(T* p) {
  return Proxy<T>(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');
  }
}


以上是是否可以在C++中实现DefaultIfNull函数?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>