首先,我可能以错误的方式写下了英语,因为它不是第一语言。对此感到抱歉。
无论如何,现在我面临多次检查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...
}
但是,它仍然需要放一行来访问子obj。 有什么办法可以像下面这样吗?
void foo(A* got)
{
C* c = got->getB()->getC();
// if got or got->getB() is nullptr, c will got assigned to nullptr.
// without exception.
// do something...
}
听说过沉船的anipatt。但要坚持当前的班级设计。
谢谢。
C.F,我以为第一个溶胶就是在每种吸气剂方法上都加上“ if stmt”。
B* A::getB() {
if(!this) return nullptr;
}
并且它在大多数情况下都有效,但不适用于虚拟方法。 (当然,因为有vtable)。
编辑: 很抱歉为它留下了味精。 即使我使用ref代替,仍然有一些时间检查指针我持有什么。 而且我之前曾提出过例外的想法,但担心这样做对我来说太昂贵了。 最好是使用nullobject或我的宏来解决。
感谢您的所有答复。
编辑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.
用法
// as-is:
A* a = getA();
if(!a) return;
B* b = a.getB();
if(!b) return;
C* c = b.getC();
if(!c) return;
// to-be:
C* c = WRD_GET(getA(), getB(), getC()); // when I can recognize those commas into dots, It's pretty intuitive to me.
WRD_GET已被设计来避免重复通话问题。 这次,新的WRD_GET宏通过获取对您给出的空传播页的提示而扩展为接受多个访问。谢谢。
无论如何,即使它可以避免重复调用,它仍然需要放置和调用其他g / setter,并且可能会稍微降低性能。和代码可以通过像FOR宏来改进。 但是,这对我的环境很有效。
答案 0 :(得分:2)
实际上,this
指针不能为空(如果在指定this
时引用已经存在,则将是一个引用),因此if检查无效:
B* A::getB()
{
if(!this) return nullptr;
}
因为访问没有静态对象的非静态成员函数是未定义的行为:
A* a = nullptr;
a->getB(); // UB/illegal, whether you have the check inside getB or not!!!
如果要避免检查空指针,请使用引用:
foo(A& a)
{
B& b = a.getB();
}
使用
B& A::getB() { return *_b; }
// but you need to guarantee that _b is never a null pointer!
// or you make, if it is never changed anyway, _b a reference as well
如果不能保证_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
{
B& b = a.getB();
// use b
}
catch(std::runtime_error const&)
{
// handle b not existing
}
当然,您不需要将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对象是一种非常正常的情况,则您可能希望保留显式检查指针的方法。