C++ 如何避免对具有组合关系的对象执行空检查

C++ 如何避免对具有组合关系的对象执行空检查,c++,C++,首先,我可能写错了英语,因为它不是第一语言。对此我感到抱歉 无论如何,现在我面临着多次检查null的问题,因为我愿意将许多对象作为它的成员来处理。 让我给你举个例子 // def: class A { public: B* getB() { return _b; } D* getD() { return _d; } private: B* _b; }; class B { public: C* getC() { return _c; } // Train

首先,我可能写错了英语,因为它不是第一语言。对此我感到抱歉

无论如何,现在我面临着多次检查null的问题,因为我愿意将许多对象作为它的成员来处理。 让我给你举个例子

// def:
class A {
public:
    B* getB() { return _b; }
    D* getD() { return _d; }

private:
    B* _b;  
};

class B {
public:
    C* getC() { return _c; } // Train Wreck. but it's needed on my frw...
   ...
};

// usg:
void foo(A* got)
{
    if(!got)
         return;
    B* b = got->getB();
    if(!b)
         return;
    C* c = b->getC();
    if(!c)
         return;
    // do something...
}
正如您所看到的,每次尝试访问成员数据时,都会有3行。 当然,我可以用宏来减少这些行

#define WRD_GET_3(expr, res, ret) \
        _TGet<decltype(expr)>::set(expr);\
        WRD_IS_NULL_3(_TGet<decltype(expr)>::get(), res, ret)
#define WRD_GET_2(expr, ret) WRD_GET_3(expr, WRD_SPACE, ret)
#define WRD_GET_1(expr)  WRD_GET_2(expr, WRD_SPACE)
#define WRD_GET(...)     WRD_OVERLOAD(WRD_GET, __VA_ARGS__)

#define WRD_IS_NULL_3(VALUE, RES, RET)  \
    if((VALUE).isNull()) {              \
        RES.warn(#VALUE);               \
        return RET;                     \
    }

template <typename T>
class _TGet {
public:
    static T& set(T& expr) { return *store() = &expr, get(); }
    static T& get() { return **store(); }
    static T** store() {
        static T* inner = 0;
        return &inner;
    }
};


void foo(A* got)
{
    if(!got) return;
    B* b = WRD_GET(got->getB())
    C* c = WRD_GET(b->getC())
    if(!c) return;

    // do something...
}
听说了火车失事的事。但我想坚持当前的类设计

提前谢谢

C.F,我认为的第一个溶胶是在每个getter方法上都加上“if stmt”

B* A::getB() {
    if(!this) return nullptr;
}
它在大多数情况下都有效,但不适用于虚拟方法。(当然,因为vtable)

编辑: 对不起,迟了给你留言。 即使我改用ref,仍然有时间检查我持有的指针。 我以前也有过例外的想法,但我担心这样做太贵了。 最好使用nullobject或my macro来解决此问题

谢谢你的回复



编辑2: 出于某种原因,我回到了这个任务中,通过重用上面编写的WRD_GET宏,为我想要做的事情制作了一个宏

#define _PUT(exp) _TGet<TypeTrait<decltype(exp)>::Org>::set(exp)
#define _GET(exp) _TGet<TypeTrait<decltype(exp)>::Org>::get()
#define _NULR(exp) nulr<TypeTrait<decltype(exp)>::Org>()
#define WRD_GET_1(e1) _PUT(e1)
#define WRD_GET_2(e1, e2) WRD_GET_1(e1).isNull() ? _NULR(e1.e2) : _PUT(e1.e2)
#define WRD_GET_3(e1, e2, e3) _PUT(e1).isNull() ? _NULR(e1.e2.e3) : (_PUT(e1.e2).isNull() ? _NULR(e1.e2.e3) : _PUT(e1.e2.e3))
#define WRD_GET_4(e1, e2, e3, e4) _PUT(e1).isNull() ? _NULR(e1.e2.e3.e4) : (_PUT(e1.e2).isNull() ? _NULR(e1.e2.e3.e4) : (_PUT(e1.e2.e3).isNull() ? _NULR(e1.e2.e3.e4) : _PUT(e1.e2.e3.e4)))
#define WRD_GET(...)            WRD_OVERLOAD(WRD_GET, __VA_ARGS__)
// yes... it's looks dumb.
WRD_GET的设计已经避免了双重调用问题。 这一次,新的WRD_GET宏被扩展,通过获取对您给出的null传播页面的提示,可以接受多个访问。多谢各位

无论如何,即使它可以避免双重调用,它仍然需要放置和调用额外的g/setter,并且可能会稍微降低性能。对于宏,可以通过like对代码进行改进。
但是,在my env.

中有效的方法是,实际上,
指针不能为空(如果指定此时已存在引用,则它将是引用),因此您的if检查无效:

B* A::getB()
{
    if(!this) return nullptr;
}
因为在没有真实对象的情况下访问非静态成员函数是未定义的行为:

如果要避免检查空指针,请使用引用:

foo(A& a)
{
   B& b = a.getB();
}

如果您不能保证_b存在,您也可以抛出:

B& A::getB()
{
    if(_b)
        return *_b;
    throw std::runtime_error("illegal state");
}
这样,如果函数返回,就可以保证获得有效的引用。不过,这种方法会迫使您在其他某个点捕获异常:

B* b = a.getB(); // assuming a being a reference already...
if(b)
{
    // use b
}
else
{
    // handle b not existing
}
vs

当然,您不需要在每个访问者周围放置try/catch,但可以在更大的块周围放置try/catch:

try
{
    B& b = a.getB();
    C& c = b.getC();
    D& d = c.getD();
    // use d

    // even this is valid:
    D& dd = a.getB().getC().getD();
    // as the exception will prevent the next function calls in the chain
}
catch(std::runtime_error const&)
{
}
您甚至可以让异常进一步传播,只在以后捕获它(在'foo'之外)

至少,您应该在main中捕获它,以确保调用堆栈上对象的析构函数(从而使RAII工作)

请注意,对于更改的
foo
签名,您可能需要检查foo之外的指针:

A a;
foo(a); // fine; especially: no check that is not needed

A* p = ...;
if(p)        // needed unless that you are 100% sure that the pointer is valid
    foo(*p);

不过,在某些系统上,异常处理可能会很昂贵。如果A不能提供B对象是一种相当正常/常见的情况,那么您可能更愿意保持显式检查指针。

在返回指针的访问器方法中,如果指针为null,则抛出null指针异常。这将限制您对访问器的检查,任何包含空指针的链式访问器都将抛出,然后您可以捕获它。如果要抛出,返回引用可能比返回指针更好。i、 如果指针不为空,则返回该指针的解引用,否则抛出。可能的重复,为什么不使用引用而不是指针?
B& A::getB()
{
    if(_b)
        return *_b;
    throw std::runtime_error("illegal state");
}
B* b = a.getB(); // assuming a being a reference already...
if(b)
{
    // use b
}
else
{
    // handle b not existing
}
try
{
    B& b = a.getB();
    // use b
}
catch(std::runtime_error const&)
{
    // handle b not existing
}
try
{
    B& b = a.getB();
    C& c = b.getC();
    D& d = c.getD();
    // use d

    // even this is valid:
    D& dd = a.getB().getC().getD();
    // as the exception will prevent the next function calls in the chain
}
catch(std::runtime_error const&)
{
}
A a;
foo(a); // fine; especially: no check that is not needed

A* p = ...;
if(p)        // needed unless that you are 100% sure that the pointer is valid
    foo(*p);