【问题标题】:How to properly use protected in singly linked list如何在单链表中正确使用protected
【发布时间】:2014-01-02 14:36:53
【问题描述】:

我已通过以下程序成功编写了单链表: 头文件为:

#ifndef SLL_H_
#define SLL_H_

#include <iostream>
class node {
    protected:

    public:
        int key;
        node *next;
        node();
        ~node();
};

class SLL : public node{
    private:
        node *Head = NULL;
        int SLL_SIZE = 0;
    public:
        //Constructor
        SLL();
        //SLL(int n);
        //Destructor
        ~SLL();
        //Modifiers
        void Push_Front(int a);
        void Push_Back(SLL A,int b);
        void Traverse();
        //Access function
        int SLL_size();
        int Get(node* p);
        //Iterator
        node* Begin();
        node* End();
        //void Search(int a);

};

#endif

SLL.cpp

#include "SLL.h"
#include <iostream>

using namespace std;

node::node(){
    cout << "Empty constructor of node is being called;" << endl;
}

node::~node(){
    cout << "Empty destructor of node is being called;" << endl;
}

SLL::SLL():node(){
    cout << "Empty constructor of SLL is being called;" << endl;
}

SLL::~SLL(){
    cout << "Empty destructor of SLL is being called." << endl;
}

//Insert element at the front of the list
void SLL::Push_Front(int k){
    node *temp = new node [1];
    temp->key = k;
    temp->next = Head;
    Head = temp;
    SLL_SIZE = SLL_SIZE + 1;
}

//Insert element at the end of the list
void SLL::Push_Back(SLL A, int m){
    node *temp1 = A.End();
    node *temp2 = new node [1];
    temp2->key = m;
    temp1->next = temp2;
    temp2->next = NULL;
    SLL_SIZE = SLL_SIZE + 1;
}

//Insert element at a given position

//Return the number of elements in the linked list
int SLL::SLL_size(){
    return SLL_SIZE;
}

//Traverse the list (print the list)
void SLL::Traverse(){
    node *temp;
    temp = Head;
    while(temp!=NULL){
        cout << temp->key << " ";
        temp = temp->next;
    }
    cout << endl;
}

//Get key given pionter
int SLL::Get(node* pt){
    if(pt!=NULL){
        node* temp = pt;
        return temp->key;
    }
    else {
        cout << "Null pointer points to nowhere!" << endl;
        return 0;
    }
}

//Return the pointer at the beginning of the list
node* SLL::Begin(){
    return Head;
}

//Return the pointer at the end of the list
node* SLL::End(){
    node* temp = Head;
    while(temp->next!=NULL){
        temp = temp->next;
    }
    return temp;
}

main.cpp

#include <iostream>
#include "SLL.h"

using namespace std;

int main()
{
    SLL A;
    A.Push_Front(1);
    A.Push_Front(2);
    A.Push_Front(5);
    A.Push_Front(6);
    A.Push_Back(A,3);
    A.Traverse();
    cout << A.SLL_size() << endl;
    cout << A.Get(A.Begin()) << endl;
    cout << A.Get(A.End()) << endl;
    return 0;
}

一个错误是,例如:

SLL.h||In member function 'void SLL::Push_Front(int)':|
SLL.h|7|error: 'int node::key' is protected|
SLL.cpp|25|error: within this context|
SLL.h|8|error: 'node* node::next' is protected|
SLL.cpp|26|error: within this context|
SLL.h||In member function 'void SLL::Push_Back(SLL, int)':|
SLL.h|7|error: 'int node::key' is protected|
SLL.cpp|35|error: within this context|
SLL.h|8|error: 'node* node::next' is protected|
LL.cpp|36|error: within this context|
SLL.h|8|error: 'node* node::next' is protected|
SLL.cpp|37|error: within this context|

使用 key 和 next 的其他成员函数也出现类似错误。

这个程序现在运行良好。但是,在我将node 类中的两行,int key; node *next; 移动到protected 下之后,它给了我诸如“node::key is protected”之类的错误。

首先,请不要责怪我做了一些愚蠢的事情:P。我知道如果我 struct 为节点,那么生活会容易得多。我正在尝试练习继承并理解受保护的。这就是为什么。

根据定义,派生类可以访问受保护的成员,对吗?我不知道我哪里做错了。

希望你能帮助我。谢谢!

【问题讨论】:

  • 如何显示发生错误的行以及您遇到的实际编译错误?
  • 看来您是在尝试从类外部设置 keynext,而不是从类中设置方法。
  • @Cancan :你应该可以,是的。但是,您还没有向我们展示触发错误的实际代码。
  • @Cancan :好吧,试着把注意力集中在实际给出错误的代码段上。
  • 为什么不直接将node 移动到SLL 的私有部分并完全摆脱继承?您当前的设计违反了 liskov 替换原则 -- SLL 包含 nodes 但它本身不是 node 的类型。

标签: c++ class inheritance protected


【解决方案1】:

protected 关键字允许继承类查看protected 成员。这意味着继承类的实例可以看到自己的受保护成员,以及同一继承类的其他实例的受保护成员。它不会通过指向父类类型的指针来扩展这种访问,因为这种访问是不安全的。

让我们把它具体化。考虑以下示例:

class A
{
    protected:
        int a_int;
};

class B : public A
{
    public:

        int good()
        {
            return a_int;  // OK:  Protected member of this instance
        }

        int bad( A *a_ptr )
        {
            return a_ptr->a_int; // BAD:  Can't access the protected member
                                 //       through a pointer to the parent class type.
        }

        int also_good( B *b_ptr )
        {
            return b_ptr->a_int; // OK:  Pointer to the same class type as this
                                 //      class is safe.
        }
};

您的代码中的错误类似于第二种情况。那么为什么第二种情况是非法的,而第三种情况却可以呢?

第二种情况是非法的,因为编译器不知道A* 指向的对象的实际类型。它可能是A任何后代,甚至可能无法转换为B*。因此,protected 子句扩展的访问不能保证是安全的或有意义的。例如,假设你有

class C : public A { ... };
class D : public C { ... };

并且您将C*D* 传递给上面的方法bad()B 应该能够看到暴露于C 的受保护成员似乎不合理,因为CB 没有直接关系。 D 也是如此。

但是,在第三种情况下,编译器肯定知道它有一个指向B 或派生自B 的类的指针,因此它知道protected 关键字扩展的访问是安全且有意义的.我的意思是,受保护的字段以B 期望的方式进行管理。事实上,如果没有这种访问权限,您将很难编写涉及两个 B 实例的二元运算符

有意义吗?

如果您仍然不相信:假设我创建了两个并行类,它们都继承自 node

// plain singly linked list
class normal_sll : public node { };

// singly linked list that stores all of its elements negated
class negative_sll : public node { };

当然,这是一个人为的例子,但请耐心等待。因为这两个类都派生自node,所以您可以通过node * 传递任何一个类。因此,您可以将negative_sll 的实例传递给normal_sll,反之亦然。

但是,C++ 的访问控制阻止任一类通过 node * 查看受保护的字段。这很好,因为negative_sll 管理它们的方式与normal_sll 不同。

但是,您不能通过normal_sll* 传递negative_sll 的实例,反之亦然。因此,您知道如果您在normal_sll 的方法之一中有一个normal_sll*,您就知道访问受保护的成员是安全的。

当然,这是一个人为的例子。我相信你能想到一个更好的。有道理吗?

现在您可以将B 设为friend of A 并覆盖此控件。但是,这会让B 看到Aprivate 成员,完全绕过protected 的概念。更好的解决方案是重写您的 SLL 代码,以便将 SLL* 而不是 node* 传递给它的方法。

【讨论】:

    【解决方案2】:

    首先,我不明白为什么要从Node 派生SLL,因为List 不是Node 的特化。您可能想要做的是让 SLL 成为 Node 的朋友:

    class node {
    protected:
        int key;
        node *next;
    public:
        friend SLL; //This allows SLL to access node's private and protected members
    
        node();
        ~node();
    };
    

    至于你的问题 - 为什么SLL 不能访问Node 的受保护成员 - 关心:

    --------- 编辑:我的解释是错误的------------

    【讨论】:

    • 使SLL 成为friend 绕过所有访问控制,这几乎使protected 无关紧要。
    • @JoeZ:是的,当然你可以将它们设为私有。我只是想尽量减少对 OP 示例代码的更改。还是我误会了你?
    • 它将最大限度地减少对 OP 代码的更改,但不会使 OP 的代码更正确。我相信 OP 的更好答案是在 SLL 的实现中将 node* 引用更改为 SLL*
    • 我仍然认为从节点派生 SLL 没有意义,因为节点是 SLL 的一部分,但 SLL 不是节点。但是,我完全同意您对 OP 代码为何不起作用的解释。
    • 我同意nodeSLL 之间的职责分离并不自然。您似乎更希望将 containercontained type 分开,而不是像之前那样将 datamechanism 分开在这里。
    猜你喜欢
    • 2021-02-19
    • 2016-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    相关资源
    最近更新 更多