Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在访问类的树层次结构中的对象时避免空指针_C++_Algorithm_Oop_C++11 - Fatal编程技术网

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);