C++ 在访问类的树层次结构中的对象时避免空指针
我必须使用基于树的对象层次结构,在这里我需要访问包含应用程序所需数据的最深元素。我不确定前面的陈述是否最好地解释了问题本身,所以最好用一个例子来说明。鉴于以下情况:C++ 在访问类的树层次结构中的对象时避免空指针,c++,algorithm,oop,c++11,C++,Algorithm,Oop,C++11,我必须使用基于树的对象层次结构,在这里我需要访问包含应用程序所需数据的最深元素。我不确定前面的陈述是否最好地解释了问题本身,所以最好用一个例子来说明。鉴于以下情况: class A { private: B* _b; public: B* getB() {return _b;} }; class B { private: C* _c; public: C* getC() {return _c;} }; class C { private: int _n
class A {
private:
B* _b;
public:
B* getB() {return _b;}
};
class B {
private:
C* _c;
public:
C* getC() {return _c;}
};
class C {
private:
int _n;
public:
int getN() {return _n;}
};
所需的操作是通过A访问n。因此,我称之为:
A foo;
foo.getB()->getC()->getN();
当路径的任何部分为空时,问题就出现了,我们最终会有一个核心转储。在上面的场景中,如果B为null,我们将进入一个核心转储场景
因此,我寻求有关任何策略或模式的帮助和建议,我可以使用这些策略或模式来避免这种核心转储场景。如果路径非常大,我会检查每一个指针是否有效,并最终得到非常难看的代码,还有可能会错过检查路径的一部分注意:我无权更改类(A、B、C)层次结构的实现,因为它们是生成代码的,我无权更改生成器。更改前一个是我的首选解决方案,但遗憾的是我不能。问题不在于指针为
null
。事实上,这很好,因为它在运行时大多会崩溃。如果它不是null
,但以前已被释放/删除,该怎么办?这种用法可能会导致未定义的行为
如果您能够:
class A
{
private:
B& _b;
public:
A(B& b): _b{b} {}
B& getB ()
{
return _b;
}
};
或者类似的。那么你至少没有任何悬念(除非你在某处也使用了指针)
如果必须使用指针,请使用其中一个智能指针-查看是否
std::unique\u ptr
为您解决If。如果不是,那么对于共享所有权,使用std::shared_ptr
等等。还要确保初始化对象的方式不会导致默认的空值。您需要一直测试:
A foo;
B* BPrt = foo.getB();
if (BPrt)
{
C* CPtr = BPrt->getC();
if (CPtr)
{
int n = CPtr->getN();
...
如果可能,您可以确保始终初始化指针:
class C {
private:
int _n = 0;
public:
int getN() {return _n;}
};
class B {
private:
static C default_c;
C* _c = &default_c;
public:
C& getC() {return *_c;}
};
C B::default_c; // An out-of-line static member definition is required.
class A {
private:
static B default_b;
B* _b = &default_b;
public:
B& getB() {return *_b;}
};
B A::default_b; // An out-of-line static member definition is required.
int main() {
A a;
std::cout << a.getB().getC().getN() << '\n';
}
C类{
私人:
int _n=0;
公众:
int getN(){return\u n;}
};
B类{
私人:
静态C默认值_C;
C*\u C=&default\u C;
公众:
C&getC(){return*\u C;}
};
C B::默认值_C;//需要一个不一致的静态成员定义。
甲级{
私人:
静态B默认值B;
B*_B=&default_B;
公众:
B&getB(){return*B;}
};
B A::默认值_B;//需要一个不一致的静态成员定义。
int main(){
A A;
std::cout为了避免使用空指针,您可能需要建立一个类不变量,使成员永远不为空。这可以通过以下步骤实现:
封装对成员的访问,以便类外的任何内容都不能设置成员。您已经通过使用私有访问实现了这一点。只需确保将成员函数中指向该成员的引用或指针传递/返回到外部即可
确保没有成员或友元函数将成员设置为null
还要确保始终初始化成员。这是通过使用自定义构造函数实现的。例如:
虽然引用具有不为null的优良特性,但它们作为成员很棘手,因为它们也是不可赋值的。作为构造函数的参数,它们很棘手,因为类对象通常不习惯或不希望将对对象的引用传递给构造函数。因此,我主张在这种情况下,即使null也是不可取的
注意:我无权更改类(A、B、C)层次结构的实现,因为它们是生成的代码,我无权更改生成器
在这种情况下,您可以使用更好的类包装生成的类:
class AWrapper {
A a;
// custom implementation that encapsulates A
}
如果空指针是无法避免的有效值,那么这种不变量当然是不可能的。在这种情况下,在间接通过指针之前,必须始终检查指针是否为空:
if (B* b = foo.getB())
if (C* c = b->getC())
c->getN();
另一个你可能会考虑的是这些指针是否都是必要的。如果这些类包含彼此而不是间接地互相引用,也许会更简单。
< p>这里我是如何解决这个问题的:
#include <iostream>
using namespace std;
class C {
private:
int _n;
public:
int getN() {return _n;}
};
class B {
private:
C* _c;
public:
C* getC() {return _c;}
};
class A {
private:
B* _b;
public:
B* getB() {return _b;}
};
int main(void);
int main() {
static B b;
static C c;
static A foo;
unsigned int n;
B *bPtr; C *cPtr;
/* --RECODE (CHAIN-CALL): foo.getB()->getC()->getN();-- */
bPtr = (B *) (foo.getB());
cPtr = (C *) (bPtr ? bPtr->getC() : 0);
n = (int) (cPtr ? cPtr->getN() : 0);
/* --USE (CAST) and (TERNARY) instead of (CHAIN-CALL)-- */
cout << n << endl;
return n;
}
#包括
使用名称空间std;
C类{
私人:
国际;;
公众:
int getN(){return\u n;}
};
B类{
私人:
C*(u C);
公众:
C*getC(){return\u C;}
};
甲级{
私人:
B*_B;
公众:
B*getB(){return\u B;}
};
int main(无效);
int main(){
静态B;
静态C;
静态A foo;
无符号整数n;
B*bPtr;C*cPtr;
/*--重新编码(链式调用):foo.getB()->getC()->getN();--*/
bPtr=(B*)(foo.getB());
cPtr=(C*)(bPtr?bPtr->getC():0);
n=(int)(cPtr?cPtr->getN():0);
/*--使用(CAST)和(三元)代替(CHAIN-CALL)-*/
您是否考虑过:
try {
foo.getB()->getC()->getN();
}
catch(...)
{
//Here you know something is Null
}
在处理现有代码时,这似乎是最简单最安全的方法。如果不能更改类,则可以通过模板进行检查:
template <typename T, typename A, typename ...Args>
auto recurse(T t, A a, Args... args)
{
if (!t)
throw std::exception{};
auto next = (t->*a)();
if constexpr (sizeof...(Args) > 0)
return recurse(next, args...);
else
return next;
}
为什么指针在第一个位置是无效的?这些对象的生命是什么?谁管理这些指针?它们是任何不变量的一部分吗?是的,不幸的是,因为你用这种方式设计了架构,代码将变得丑陋。C++没有“安全导航操作符”。就像C#那样。你必须自己实现。但更好的选择是大幅重构你的体系结构。你应该尽量避免多层耦合,因为处理它们总是一场噩梦。这不是答案,但:德米特定律表明当您发现自己必须像那样链接多个取消引用时,是时候重构API了,这样您就不必再这样做了:@JeremyFriesner在“干净的代码”中被称为“。引用类型数据成员很麻烦。因为它们不能被删除
template <typename T, typename A, typename ...Args>
auto recurse(T t, A a, Args... args)
{
if (!t)
throw std::exception{};
auto next = (t->*a)();
if constexpr (sizeof...(Args) > 0)
return recurse(next, args...);
else
return next;
}
recurse(&foo, &A::getB, &B::getC, &C::getN);