【问题标题】:Is there a safe navigation operator for C++?是否有 C++ 的安全导航运算符?
【发布时间】:2017-12-22 07:36:24
【问题描述】:

在现代 C++ 中,有没有办法进行安全导航?

例如,而不是做...

if (p && p->q && p->q->r)
    p->q->r->DoSomething();

...通过使用某种短路智能指针,或某种其他类型的利用运算符重载的语法,或标准 C++ 库或 Boost 中的某些东西,具有简洁的语法。

p?->q?->r?->DoSomething(); // C++ pseudo-code.

上下文尤其是 C++17。

【问题讨论】:

  • 据我所知,并希望它不会出现。这种 a->b->c->d 编码风格会导致大问题、紧密耦合以及无法将代码片段彼此分离。
  • 这种情况下安全指针应该做什么?
  • 你可以重载operator->,但问题是它应该返回什么。
  • @Bogolt - 我同意所有提到的“大问题”方面。不幸的是,我的情况是使用一个非常大且非常古老的代码库,它普遍使用第一个示例作为模式。除了那些错过的地方。我有大问题的第一手经验。给定时间,代码库应该被更好地封装并采用“告诉,不要问”的原则。但那是在未来。
  • @manni66 - “安全导航操作员”应该短路并且不能操作。

标签: c++ c++17


【解决方案1】:

您能做的最好的事情就是将所有成员访问合并到一个函数中。这假设不检查一切都是指针:

template <class C, class PM, class... PMs>
auto access(C* c, PM pm, PMs... pms) {
    if constexpr(sizeof...(pms) == 0) {
        return c ? std::invoke(pm, c) : nullptr;
    } else {
        return c ? access(std::invoke(pm, c), pms...) : nullptr;
    }
}

让你写作:

if (auto r = access(p, &P::q, &Q::r); r) {
    r->doSomething();
}

没关系。或者,您可以对运算符重载进行一些疯狂的操作,并生成如下内容:

template <class T>
struct wrap {
    wrap(T* t) : t(t) { }
    T* t;

    template <class PM>
    auto operator->*(PM pm) {
        return ::wrap{t ? std::invoke(pm, t) : nullptr};
    }

    explicit operator bool() const { return t; }
    T* operator->() { return t; }
};

让你写:

if (auto r = wrap{p}->*&P::q->*&Q::r; r) {
    r->doSomething();
}

这也可以。不幸的是,没有 -&gt;?.? 这样的运算符,所以我们不得不在边缘工作。

【讨论】:

  • :)) 虽然绝对正确,但auto r = wrap{p}-&gt;*&amp;P::q-&gt;*&amp;Q::r; r 与用被遗忘已久的语言写成的古代手稿一样清晰易读,掩埋在数千年的尘埃中,已被两百代人盛宴蛆虫——在尚未有人发现的上锁地穴中。
  • 我把你的评论放在心上,并用一个更好的答案代替了我的答案——如果没有别的,为了喜剧效果:)
  • 重载access(C &amp;, PM pm. PMs... pms) 是否允许链接引用? (对于返回值的成员也是如此?)
  • 这很酷。是否允许使用 -&gt;*std::bind(&amp;P::f, _1, args) 调用成员函数?
  • 我将 Barry 的答案标记为已接受的答案,因为这部分“不幸的是没有 ->?或 .? 像操作员......”感谢大家考虑我的问题!如果有人想出一种方法来更好地模仿 C++ 中的安全导航运算符,那就太好了。
【解决方案2】:

“有一点样板......”

我们可以做到这一点:

p &gt;&gt; q &gt;&gt; r &gt;&gt; doSomething();

这是样板文件...

#include <iostream>

struct R {
    void doSomething()
    {
        std::cout << "something\n";
    }
};

struct Q {
    R* r;
};

struct P {
    Q* q;
};

struct get_r {};
constexpr auto r = get_r{};

struct get_q {};
constexpr auto q = get_q{};

struct do_something {
    constexpr auto operator()() const {
        return *this;
    }
};
constexpr auto doSomething = do_something {};

auto operator >> (P* p, get_q) -> Q* {
    if (p) return p->q;
    else return nullptr;
}

auto operator >> (Q* q, get_r) -> R* {
    if (q) return q->r;
    else return nullptr;
}

auto operator >> (R* r, do_something)
{
    if (r) r->doSomething();
}

void foo(P* p)
{
//if (p && p->q && p->q->r)
//    p->q->r->DoSomething();
    p >> q >> r >> doSomething();
}

生成的程序集是非常可接受的。到此为止的旅程可能不会......

foo(P*):
        test    rdi, rdi
        je      .L21
        mov     rax, QWORD PTR [rdi]
        test    rax, rax
        je      .L21
        cmp     QWORD PTR [rax], 0
        je      .L21
        mov     edx, 10
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:std::cout
        jmp     std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
.L21:
        ret

【讨论】:

  • 不会短路,但会快速失效。样板文件可以塞进一个宏中(尽管我不是宏的粉丝)。我喜欢调用者在调用点使用它的方式——非常干净,尽管它不会“惯用 C++”来解引用指针。
  • @Eljay 优化器为我们做了短路,所以这不是问题。您可能需要一个代码生成器来为整个项目执行此操作。您可以使用yacc 之类的工具快速构建它。 en.wikipedia.org/wiki/Yacc
猜你喜欢
  • 2011-09-30
  • 2011-05-05
  • 2016-04-07
  • 2012-03-13
  • 2016-04-08
  • 1970-01-01
  • 2017-05-27
  • 2015-11-22
相关资源
最近更新 更多