C++ 如何避免对具有组合关系的对象执行空检查
首先,我可能写错了英语,因为它不是第一语言。对此我感到抱歉 无论如何,现在我面临着多次检查null的问题,因为我愿意将许多对象作为它的成员来处理。 让我给你举个例子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
// 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);