C++ 双向关联:使用智能指针访问对象似乎会破坏实例

C++ bi-directional association: object access with smart pointers seems to corrupt the instance

我有一个用原始指针实现的双向对象关联,它工作得很好。然后我决定用智能指针重构我的代码,突然一个字符串成员 (depName ) class 之一 (部门) 对象初始化后不再可访问。我检查了参数是否正确传递,并且在构造函数中一切正常。成员获得了预期的价值。但此后它不再可访问,此外 之前 运行 顺利运行的代码现在崩溃 。我不知道为什么。

编辑:管理器 class 中使用的字符串变量名称似乎也发生了同样的情况。结束编辑

这是我的代码(抱歉,它太多了——我尽可能地减少了它):

头文件中:

    class Manager;
    class Department
    {
    private:
        string depName;
        shared_ptr<Manager> head;
        vector <shared_ptr<Manager>> depMembers;
    public:
        Department(string depName, shared_ptr<Manager> head);
        virtual ~Department();
        string getDepName() const;
        void setDepName(const string &value);
        void addMember(shared_ptr<Manager> newMember);
        void removeMember(shared_ptr<Manager> who);
        const shared_ptr<Manager>getHead() const;
        void setHead(shared_ptr<Manager>value);
        double sumOfIncome();
        void show();
    };
    //--------------------------------
    class Department;
    class  Manager
    {
        private:
            string name;
            float salary;
            float bonus;//Bonus ist ein Prozentsatz
            weak_ptr<Department> myDepartment;
    //        Department * myDepartment; //With this raw pointer the code still worked*
        public:
            Manager(string, float, float);
            virtual ~Manager();
            float income()const ;
            string toString()const ;
            double calcBonus() const;
            shared_ptr<Department> getMyDepartment() const;
            void setMyDepartment(shared_ptr<Department> abt);
            float getSalary() const;
            string getName() const;
    };

cpp 文件中: department.cpp

    //---------------------------------------------------
    Department::Department(string depName, shared_ptr<Manager>head)
        :depName(depName),head(nullptr)
    {
        this->setHead(head);
        cout << "\nIn constructor parameter depName: " + depName;
        cout << "\n instancevariable " + this->depName << endl;
    }
    //--------------------------------
    Department::~Department()
    {}
    //--------------------------------
    string Department::getDepName() const
    {
        return this->depName;
    }
    //--------------------------------
    void Department::setDepName(const string &value)
    {
        depName = value;
    }
    //--------------------------------
    void Department::addMember(shared_ptr<Manager> newMember)
    {
        depMembers.push_back(newMember);
    }
    //--------------------------------
    void Department::removeMember(shared_ptr<Manager> who)
    {
        vector<shared_ptr<Manager>>::iterator itMember = depMembers.begin();
        //Iterator must be dereferenced to access data
        while( *itMember != who){
            itMember++;
        }
        if( *itMember == who)
            depMembers.erase( itMember);
    }
    //--------------------------------
    const shared_ptr<Manager> Department::getHead() const
    {
        return head;
    }
    //--------------------------------
    void Department::setHead(shared_ptr<Manager>value)
    {
        if( head != nullptr && head->getMyDepartment()!= nullptr)
            head->setMyDepartment(nullptr);//department of old head is deleted

        //new head of department assigned
        head = value;
        //bidirektionaler access
        if(head !=nullptr)
            head->setMyDepartment( shared_ptr<Department>(this));
    }
    //--------------------------------
    double Department::sumOfIncome()
    {
        double sum = 0;
        for(unsigned int i=0; i < depMembers.size(); i++){
            sum += depMembers[i]->getSalary() ;
        }
        return sum;
    }
    //--------------------------------
    void Department::show()
    {
        cout <<"----------------" << endl;
        cout << "Department: " << this->depName << " run by " << head->getName()<<endl;
        cout <<"----------------" << endl;
        cout << "Members: " << endl;
        cout <<"----------------" << endl;
        cout << head->toString() << endl;
        for( unsigned int i=0; i < depMembers.size() ; i++){
            cout <<"----------------" << endl;
            cout << depMembers[i]->toString()<< endl;
        }
        cout <<"----------------" << endl;
    }

manager.cpp

    //---------------------
    float Manager::getSalary() const
    {
        return salary;
    }
    //----------------------------------
    string Manager::getName() const
    {
        return name;
    }
    //----------------------------------
    Manager::Manager(string n, float s, float bon)
        :name(n),salary(s), bonus(bon)
    {}
    //----------------------------------
    Manager::~Manager(){}
    //----------------------------------
    float Manager::income()const
    {
        return (salary + calcBonus() );
    }
    //----------------------------------
    string Manager::toString() const
    {
        stringstream ss;
        ss << name << "\n heads the department ";
        shared_ptr<Department> dep = myDepartment.lock();
        if( dep !=nullptr)
            ss<< dep->getDepName();
        else ss << " NONE ";
        ss << "\nBonus: " << calcBonus();
        ss << "\nIncome: " << income();
        return ss.str();
    }
    //----------------------------------
    double Manager::calcBonus()const
    {
        shared_ptr<Department> dep = myDepartment.lock();
        if(dep != nullptr)
            return dep->sumOfIncome()* bonus;
        else
            return 0;
    }

    //----------------------------------
    shared_ptr<Department> Manager::getMyDepartment() const
    {
    //    if( !meineAbteilung->expired())
        return myDepartment.lock();
    }
    //----------------------------------
    void Manager::setMyDepartment( shared_ptr<Department> dep)
    {
        myDepartment = dep;
    }
    //----------------------------------

测试运行:

    int main(){
        shared_ptr<Department> itDepartment 
            = make_shared<Department>("IT",make_shared<Manager>("Julia", 66066, 0.15));

        itDepartment->show();
        return 0;
    }

它正在崩溃,因为没有人拥有某些对象。这是一个大危险信号:

head->setMyDepartment(shared_ptr<Department>(this));

enable_shared_from_this 这样的东西,但你没有使用它,所以从 this 构造一个 shared_ptr 是胡说八道,特别是因为你允许它走出去范围马上。那会调用你不想要的 delete this

有人需要从外部拥有这些对象。他们不能只拥有彼此(循环引用)。

我可以将 John Zwinck 的回答标记为正确 -(谢谢 John!),但我自己需要更多信息来理解这个问题,所以我决定 post我自己的回答。

我的方法有两个问题:

  1. shared_ptr 永远不应由原始指针变量 生成,因为每次从原始指针初始化 shared_ptr 都会创建一个新的管理器-具有自己引用计数的对象。仅当从现有管理器对象的 shared_ptrweak_ptr 之一创建新的 shared_ptr 时,才会使用现有管理器对象。 (不知何故,我读过的书设法省略了这一重要信息。)this 是一个原始指针,因此在我的 Department 构造函数中创建了第二个管理器对象。一旦构造函数离开,第二个管理器对象的引用计数就会减少到 0 - 删除我刚刚完成创建的对象。因此,永远不应从 this 本身创建 shared_ptr。这是一个致命的缺陷。
  2. 由于在双向关联中对象需要传递指向自身的指针,C++ 库包含一个 class enable_shared_from_this,它提供了一些模板魔术在后台从中制作 shared_ptr。不幸的是 shared_from_this() 在构造函数 中不可用,因为对象构建尚未完成。结论是,一个对象不能在构造函数中将对自身的引用传递给另一个对象。只是没有办法让它发挥作用。 一种解决方法,在构造函数之后直接调用setHead()是出路。来自Java,其中从构造函数传递this是常态,这是你不得不忍受的事情...

除了 John Zwinck 将我推向正确的方向之外,我发现 David Kieras 的一篇论文非常有帮助: http://www.umich.edu/~eecs381/handouts/C++11_smart_ptrs.pdf