C++ 双向关联:使用智能指针访问对象似乎会破坏实例
C++ bi-directional association: object access with smart pointers seems to corrupt the instance
我有一个用原始指针实现的双向对象关联,它工作得很好。然后我决定用智能指针重构我的代码,突然一个字符串成员 (depName ) class 之一 (部门) 对象初始化后不再可访问。我检查了参数是否正确传递,并且在构造函数中一切正常。成员获得了预期的价值。但此后它不再可访问,此外 之前 运行 顺利运行的代码现在崩溃 。我不知道为什么。
编辑:管理器 class 中使用的字符串变量名称似乎也发生了同样的情况。结束编辑
- 代码编译没有错误甚至没有警告。
- 我正在使用 qt creator 3.3.0 和 mingw 编译器 5.4.0
- 系统声称有一个"Segmentation fault"。
这是我的代码(抱歉,它太多了——我尽可能地减少了它):
在头文件中:
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我自己的回答。
我的方法有两个问题:
shared_ptr
永远不应由原始指针变量 生成,因为每次从原始指针初始化 shared_ptr
都会创建一个新的管理器-具有自己引用计数的对象。仅当从现有管理器对象的 shared_ptr
或 weak_ptr
之一创建新的 shared_ptr
时,才会使用现有管理器对象。 (不知何故,我读过的书设法省略了这一重要信息。)this
是一个原始指针,因此在我的 Department 构造函数中创建了第二个管理器对象。一旦构造函数离开,第二个管理器对象的引用计数就会减少到 0 - 删除我刚刚完成创建的对象。因此,永远不应从 this
本身创建 shared_ptr
。这是一个致命的缺陷。
- 由于在双向关联中对象需要传递指向自身的指针,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
我有一个用原始指针实现的双向对象关联,它工作得很好。然后我决定用智能指针重构我的代码,突然一个字符串成员 (depName ) class 之一 (部门) 对象初始化后不再可访问。我检查了参数是否正确传递,并且在构造函数中一切正常。成员获得了预期的价值。但此后它不再可访问,此外 之前 运行 顺利运行的代码现在崩溃 。我不知道为什么。
编辑:管理器 class 中使用的字符串变量名称似乎也发生了同样的情况。结束编辑
- 代码编译没有错误甚至没有警告。
- 我正在使用 qt creator 3.3.0 和 mingw 编译器 5.4.0
- 系统声称有一个"Segmentation fault"。
这是我的代码(抱歉,它太多了——我尽可能地减少了它):
在头文件中:
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我自己的回答。
我的方法有两个问题:
shared_ptr
永远不应由原始指针变量 生成,因为每次从原始指针初始化shared_ptr
都会创建一个新的管理器-具有自己引用计数的对象。仅当从现有管理器对象的shared_ptr
或weak_ptr
之一创建新的shared_ptr
时,才会使用现有管理器对象。 (不知何故,我读过的书设法省略了这一重要信息。)this
是一个原始指针,因此在我的 Department 构造函数中创建了第二个管理器对象。一旦构造函数离开,第二个管理器对象的引用计数就会减少到 0 - 删除我刚刚完成创建的对象。因此,永远不应从this
本身创建shared_ptr
。这是一个致命的缺陷。- 由于在双向关联中对象需要传递指向自身的指针,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