【问题标题】:C++,runtime error: member call on null pointer of typeC++,运行时错误:对空指针类型的成员调用
【发布时间】:2020-09-23 03:37:43
【问题描述】:

首先,我是 C++ 新手,我正在努力学习它。也是stackoverflow的新手。 很难说实话。 如果您对我的代码以及如何改进它有更多的 cmets,请告诉我,因为我仍在学习过程中。

好吧,我只是使用面向对象编程创建了一个在线预订系统。

好的,主要问题是我不明白为什么 system.setDisplay(1234); 没有打印任何东西。我已经尝试了一切,只是没有加起来。 OnlineBookingSystem 是用于调用 setDisplay(id) 的类,然后调用显示类。 如果你能提供帮助,这对我来说意味着世界,我得到的错误是:

运行时错误:对“用户”类型的空指针的成员调用 (solution.cpp) 摘要:UndefinedBehaviorSanitizer:undefined-behavior prog_joined.cpp:179:54

#include <vector>
#include <string>
#include <iostream>
#include <memory>
#include <queue>
using namespace std;

enum class BookGenre
{
    Horror,Adventure,Romance,Comic
};
class Book
{
    private:
        BookGenre genre;
        string title;
        size_t id;
    public:
        Book(string title,size_t id,BookGenre genre):title(title),id(id),genre(genre){}
        string getTitle(){return title;}
        size_t getId(){return id;}
        BookGenre getGenre(){return genre; }
};
class Library
{
    private:
        vector<shared_ptr<Book>> listOfBooks;
    public:
        Library(){};
        void addBook(string title,size_t id,BookGenre genre)
        {
            listOfBooks.push_back(make_shared<Book>(title,id,genre));
        }
        shared_ptr<Book> getBook(size_t id)
        {
            for(auto&x:listOfBooks)
            {
                if(x->getId()==id)
                {
                    return x;
                }
            }
            return nullptr;
        }
        void removeBook(size_t id)
        {
            for(auto it=listOfBooks.begin();it!=listOfBooks.end();it++)
            {
                if((*it)->getId()==id)
                {
                    listOfBooks.erase(it);
                }
            }
        }
};
class User
{
    protected:
        size_t id;
        string username;
     
    public:
        User(size_t id,string username):id(id),username(username)
        {
 
        }
        virtual ~User(){}
        size_t getId(){return id;}
        string getUsername(){return username;}

};
class Employee:public User{
    private:
        double salary;
    public:
        Employee(size_t id,string username,double salary):User(id,username),salary(salary)
        {
        }
        void setSalary(double salary)
        {
            this->salary=salary;
        }
        double getSalary(){return salary;}
     
};
class Customer:public User{
    private:
           bool membership;
    public:
        Customer(size_t id,string username):User(id,username)
        {
            membership=false;
        }
        void setMemberActive()
        {
            membership=true;
        }
        bool isMemberActive()
        {
            return membership;
        }
};
class UserManager
{
    private:
        vector<shared_ptr<User>>listOfUsers;
        queue<shared_ptr<Customer>>queue;
    public:
        UserManager()
        {

        }
      
        void addCustomer(size_t id,string username)
        {
            listOfUsers.push_back(make_shared<Customer>(id,username));
        }
        void removeCustomer(string username)
        {
            for(auto it=listOfUsers.begin();it!=listOfUsers.end();it++)
            {
                if(dynamic_pointer_cast<Customer>(*it))
                {
                    if((*it)->getUsername()==username)
                    {
                        listOfUsers.erase(it);
                    }
                }
            }
        }
        shared_ptr<Customer> getCustomer(string username)
        {
            for(auto it=listOfUsers.begin();it!=listOfUsers.end();it++)
            {
                if(dynamic_pointer_cast<Customer>(*it))
                {
                    if((*it)->getUsername()==username)
                    {
                        return dynamic_pointer_cast<Customer>(*it);
                    }
                }
            }
            return nullptr;
        }
        void addToQueue(string username)
        {
            queue.push(getCustomer(username));
        }
        void removeCurrentCustomer()
        {
            queue.pop();
        }
        shared_ptr<Customer> getNextCustomer()
        {
            if(queue.empty())
            {
                return nullptr;
            }
            return queue.front();
        }
        /*
            same process for user;
        */
};
class Display
{   
    private:
        shared_ptr<Customer> m_customer;
        shared_ptr<Book> m_book;
    public:
        Display(shared_ptr<Customer> _customer,shared_ptr<Book> _book ):m_customer(_customer),m_book(_book)
        {

        }
        shared_ptr<Customer> getUser(){return m_customer;}
        shared_ptr<Book> getBook(){return m_book;}
        void displayInfo()
        {
            cout<<"Customer username: "<<m_customer->getUsername()<<endl;
            cout<<"Member Active: "<<m_customer->isMemberActive();
            cout<<"book id: "<<m_book->getId()<<endl;
            cout<<"book title: "<< m_book->getTitle()<<endl;
        }

};
class OnlineBookingSystem
{
    private:
        UserManager manager;
        Library library;
        shared_ptr<Display>display;
    public:
        OnlineBookingSystem()
        {
            UserManager manager;
            Library library;
            this->manager=manager;
            this->library=library;
            this->display=nullptr;
        }
        Library getLibrary()
        {
            return library;
        }
        UserManager getUserManager()
        {
            return manager;
        }
        void  setDisplay(size_t id)
        {
            display=make_shared<Display>( manager.getNextCustomer(),library.getBook(id));
            display->displayInfo();
        }
        shared_ptr<Display> getDisplay()
        {
            return this->display;
        }
};
int main()
{

    OnlineBookingSystem system;
    auto lib=system.getLibrary();
    lib.addBook("Adventure of Pablo",1234,BookGenre::Adventure);
    auto manager=system.getUserManager();
    manager.addCustomer(2020,"Michael");
    auto _customer=  manager.getCustomer("Michael");
    _customer->setMemberActive();
    manager.addToQueue("Michael");
    system.setDisplay(1234);

    return 0;
}

【问题讨论】:

  • 不相关(这里也无所谓):不管成员初始化器列表中使用的顺序如何,成员变量都按照它们定义的顺序进行初始化。 genreBook 中首先初始化,即使它在列表中是最后一个。 Book 中的成员之间没有相互依赖关系,但是当存在时,请注意!
  • auto lib=system.getLibrary(); 是 100% 的值语义。 Library 按值返回并按值存储,因此lib.addBook("Adventure of Pablo",1234,BookGenre::Adventure); 对副本进行操作。恐怕你需要更早地开始测试你的代码。
  • @user4581301 是的,这最终成为了根本问题(但使用manager 而不是lib)。顺便说一下,关于初始化顺序的好提示;我不知道。再说一遍.. 有一段时间没做过 C++ 了 :)
  • 非常感谢,我现在很高兴,我感到很沮丧,但是是的,我会遵循所有的提示!
  • 我能给的最大建议是获得一个带有良好调试器的 IDE。使用调试器,您可以单步执行程序并逐行检查它的功能(使用好的调试器,您可以做的远不止这些)。一旦你发现程序做了一些你没有预料到的事情,通常是走错了路径或存储了错误的值,你就发现了一个错误。了解和修复错误需要做更多工作,但发现它是一个重要的早期步骤。

标签: c++ smart-pointers object-oriented-analysis


【解决方案1】:

我看到的问题:

问题 1

由于OnlineBookingSystem::getLibrary()的返回类型是Library,所以该行

auto lib=system.getLibrary();

lib 构造为system 中对象的副本。对lib 所做的任何更改都是对副本的更改,而不是对system 中的Library 对象的更改。

要解决问题,请将返回类型更改为引用:

Library& getLibrary()
{
    return library;
}

并捕获返回值作为main 中的引用。

auto& lib=system.getLibrary();

问题 2

与问题 1 类似,但这次是在OnlineBookingSystem::getUserManager。将其返回类型更改为引用:

UserManager& getUserManager()
{
    return manager;
}

并捕获返回值作为main中的引用。

auto& manager=system.getUserManager();

问题 3

在每一步都采用防御性编程,直到出现性能瓶颈。如果函数的返回值可以是nullptr,则在调用时检查返回值,当返回值确实是nullptr时处理。

OnlineBookingSystem::setDisplay 更新为:

    void  setDisplay(size_t id)
    {
        display=make_shared<Display>( manager.getNextCustomer(),library.getBook(id));
        if ( display )
        {
            display->displayInfo();
        }
        else
        {
           // Deal with the nullptr case
           std::cout << "Unable to find a book with id " << id << std::endl;
        }
    }

【讨论】:

    【解决方案2】:

    问题
    这归结为您对system.getUserManager() 的调用返回了OnlineBookingSystemmanager 字段的副本,而不是指向它的指针

    因此,当您调用manager.addToQueue("Michael") 时,它会将Michael 添加到int main() 本地的UserManager 实例,而不是system 中保存的实例。

    您的函数OnlineBookingSystem::setDisplay(size_t) 调用manager.getNextCustomer()。因为您已将 Michael 添加到 manager 的另一个实例,所以这将返回 nullptr

    如何解决
    您只需修改OnlineBookingSystem::getUserManager() 以返回指向manager 的指针而不是它的副本:

    UserManager* getUserManager()
    {
        return &manager;
    }
    

    然后修改调用代码以使用指针解引用运算符 (-&gt;) 而不是句点来访问 manager 上的方法。

    【讨论】:

    • 考虑返回引用而不是指针。
    • @user4581301 是的,你可能是对的。 OP 也在使用安全指针,所以我可能也应该这样做。如果你不介意,在这个例子中使用引用和指针之间有实际区别吗?我唯一能想到的是能够从调用代码中分配给manager,但考虑到它是私有的,也许这不是最好的选择?
    • 指针和引用之间的实际区别,可能不是。编译器可能能够更轻松地对引用应用一些优化,但仅此而已。你可以用参考做更少的事情,这为愚蠢的错误提供了额外的防御。从语义上讲,差异是由上下文加载的。对我来说最重要的是通过返回参考,读者知道OnlineBookingSystemowns the Library
    • 也就是说,我质疑返回引用或指向Library 的指针。 Library 是一个private 成员,一旦你添加了一个具有零访问限制的public 访问器,你就有效地创建了Library public。任何人都可以为所欲为,并且可以永远持有它,甚至可能比它所来自的OnlineBookingSystem 的生命周期还要长。在这里可能是正确的选择,但在这样做之前我会更加努力地考虑它。这同样适用于任何 setter 函数。
    • 我无法在评论中解释我对智能指针的看法。使用智能指针来形式化对象的所有权。我的一般建议是确定谁拥有什么物品以及何时拥有。它使其余的决定更容易做出。尽可能使用容器直接容纳对象。容器拥有对象并传递引用。在容器不能直接容纳对象的地方使用std::unique_ptr。传递对对象的引用或转移所有权。不要使用std::shared_ptr,除非您确实有多个同时拥有该对象的所有者。
    猜你喜欢
    • 2021-05-02
    • 1970-01-01
    • 1970-01-01
    • 2017-03-09
    • 2021-06-06
    • 1970-01-01
    • 2023-03-15
    • 2022-12-19
    • 1970-01-01
    相关资源
    最近更新 更多