C++:抽象指针映射似乎正在切片到抽象类
当我遇到这个问题时,我正在使用 C++ 进行内存管理。当我尝试从 Solver 类末尾的映射中删除抽象指针时,有时会发生段错误。不过,这只发生在少数值上,而不是全部。
我尽可能详尽地使用了 dbx,并且发现指针是有效的,但在失败的删除中,它们指向似乎是普通配置的内容,而不是像下面的示例 PegConfiguration 那样的内容。对所有虚拟方法(例如 toString() 甚至 isGoal())的调用在这些指针上都会失败,但非虚拟方法(例如stepsToString())可以正常工作。
这家伙的问题看起来很相似,但我无法理解那里的解决方案。他说他解决了,但没有具体说如何解决。
Solver.cpp
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include "Solver.h"
#include "Configuration.h"
using namespace std;
deque<Configuration*> Solver::solve(Configuration* initial)
{
paths.insert(pair<string,Configuration*>(initial->toString(), initial));
configs.push_back(initial);
//Continue while there are still configurations
//and the current configuration is not the goal
while(!configs.empty() &&
!configs.front()->isGoal())
{
Configuration* cfg = configs.front();
configs.pop_front();
//if the configuration cannot lead to the solution, throw away
if(!(cfg->isFailure()))
{
vector<Configuration*> newConfigs = (cfg->getNeighbors());
for(int i = 0; i < newConfigs.size(); i++)
{
Configuration* newConfig = newConfigs[i];
//if it is a new config, not in the map
if(paths.insert(pair<string, Configuration*>(newConfig->toString(), cfg)).second)
{
configs.push_back(newConfig);
}
else
{
//delete newConfig;
}
}
}
}//end while
//if there was a solution, work it out
//if there is none, return empty vector
if(!configs.empty())
{
// put goal configuration value in solutions stack
// find previous configuration in map
// put that configuration value in stack
// continue until previous configuration == current configuration
// in other words, reached initial configuration
//send solution stack back to main
//which will handle printing it out
//remove pointers from previous containers,
//so their deletion doesn't screw things up
Configuration* cfg = configs.front();
configs.pop_front();
string key;
do
{
solutions.push_front(cfg);
key = cfg->toString();
cfg = paths[key];
paths[key] = NULL;
} while(!(cfg->toString() == key));
}
//clean up
for(map<string, Configuration*>::iterator iter = paths.begin();
iter != paths.end();
iter++)
{
delete iter->second; //problem occurs HERE
}
for(int i = 0; i < configs.size(); ++i)
{
delete configs[i];
}
paths.clear();
configs.clear();
return solutions;
}//end solve
Configuration.h
#ifndef CONFIGURATION_H
#define CONFIGURATION_H
#include <string>
#include <vector>
class Configuration
{
public:
/**
* Destructor initialized as virtual so that it can be overridden by
* subclasses.
*/
virtual ~Configuration() {}
/**
* Does this configuration match the target value?
*
* @return true if match, false elsewise
*/
virtual bool isGoal() const;
/**
* Can this configuration ever be solved?
*
* @returns true if impossible to solve, false elsewise
*/
virtual bool isFailure() const = 0;
/**
* Basic string representation of configuration. Differs for each class.
*
* @returns string representation of configuration
*/
virtual std::string toString() const = 0;
/**
* Comparation operator for map-sorting. Compares configuration values.
*
* @returns true if value is greater than other's value, false elsewise
*/
bool operator<(const Configuration& other ) const;
/**
* Return all of this config's neighbors (difference of only a single step).
*
& @returns vector of new configurations
*/
virtual std::vector<Configuration*> getNeighbors() = 0;
/**
*
* @returns string representation of valid steps.
*/
std::string stepsToString();
protected:
// contains the steps that are possible for this configuration to reach
// another valid configuration
static std::vector<int> steps;
//the target configuration value
static int _goal;
//the current value of this configuration
int _value;
};//end Configuration
#endif
Configuration.cpp
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "Configuration.h"
using namespace std;
int Configuration::_goal = 1234;
vector<int> Configuration::steps;
//
// Name: isGoal()
//
bool Configuration::isGoal() const
{
return (_value == Configuration::_goal);
}
//
// Name: operator<
//
bool Configuration::operator<(const Configuration& other) const
{
bool answer = false;
if(_value < other._value) { answer = true; }
return answer;
}
//
// Name: stepsToString
//
string Configuration::stepsToString()
{
stringstream ss;
for(int i = 0; i < Configuration::steps.size(); i++)
{
ss << Configuration::steps[i] << " ";
}
return ss.str();
}
PegConfiguration.cpp
#include <vector>
#include <string>
#include <sstream>
#include "PegConfiguration.h"
using namespace std;
vector<char> PegConfiguration::goal;
/**
* Value constructor.
*
* @param value board value of new Configuration
*/
PegConfiguration::PegConfiguration(vector<char> val, int empty):
value(val),
emptyIndex(empty) {}
/**
* Copy constructor.
*
* @param configuration to copy
*/
PegConfiguration::PegConfiguration(const PegConfiguration::PegConfiguration& copy):
value(copy.value),
emptyIndex(copy.emptyIndex) {}
/**
* Constructor for initial puzzle configuration. Given the initial number
* of pegs on one side of the board, it constructs the initial and goal
* value.
*
* @param numPegs number of pegs on one side of board
*/
PegConfiguration::PegConfiguration(int numPegs):
value(2 * numPegs + 1, '.'),
emptyIndex(numPegs)
{
goal = vector<char>(2 * numPegs + 1, '.');
fill(value.begin(), value.begin() + numPegs, 'R');
fill(value.rbegin(), value.rbegin() + numPegs, 'B');
fill(goal.begin(), goal.begin() + numPegs, 'B');
fill(goal.rbegin(), goal.rbegin() + numPegs, 'R');
}
/**
* Goal configuration is an exact mirror of the initial board.
*
* @returns true if this is the goal configuration.
*/
bool PegConfiguration::isGoal() const
{
return (value == goal);
}
/**
* Is this puzzle impossible to solve? Nope.
*
* @returns false always.
*/
bool PegConfiguration::isFailure() const
{
return false;
}
/**
* Basic string representation of configuration value.
*
* @returns string representation of configuration value
*/
std::string PegConfiguration::toString() const
{
stringstream ss;
for(int i = 0; i < value.size(); ++i)
{
ss << value[i] << " ";
}
return ss.str();
}//end toString
/**
* The empty space can either move one space right or left, or jump
* two spaces right or left, in both cases swapping with the peg in that
* location. The only restriction is where the peg is -- if it's too far
* to the left, it can't jump left, for example.
*
* @returns vector of neighbor configuration pointers
*/
std::vector<Configuration*> PegConfiguration::getNeighbors()
{
vector<Configuration*> neighbors;
//jump one to the left
if((emptyIndex - 1) >= 0)
{
vector<char> newValue(value);
(newValue[emptyIndex], newValue[emptyIndex - 1]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex - 1));
}
//jump two to the left
if((emptyIndex - 2) >= 0)
{
vector<char> newValue(value);
swap(newValue[emptyIndex], newValue[emptyIndex - 2]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex - 2));
}
//jump one to the right
if((emptyIndex + 1) < value.size())
{
vector<char> newValue(value);
swap(newValue[emptyIndex], newValue[emptyIndex + 1]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex + 1));
}
//jump two to the right
if((emptyIndex + 2) < value.size())
{
vector<char> newValue(value);
swap(newValue[emptyIndex], newValue[emptyIndex + 2]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex + 2));
}
return neighbors;
}//end getNeighbors
I was in the midst of memory management in a C++ when I ran head-on into this problem. When I try to delete abstract pointers from a map at the end of the Solver class, a seg-fault will sometimes occur. This only happens for a few values, though, not all.
I used dbx as exhaustively as I know how, and I found that the pointers were valid, but on the deletes that failed they were pointing to what seems to be a vanilla Configuration, rather than something like the example PegConfiguration below. Calls to all virtual methods such as toString() and even isGoal() fail on those pointers, but non-virtual methods like stepsToString() work fine.
This guy's problem looks similar, but I can't understand the solutions there. He said he solved it, but didn't really say how.
Solver.cpp
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include "Solver.h"
#include "Configuration.h"
using namespace std;
deque<Configuration*> Solver::solve(Configuration* initial)
{
paths.insert(pair<string,Configuration*>(initial->toString(), initial));
configs.push_back(initial);
//Continue while there are still configurations
//and the current configuration is not the goal
while(!configs.empty() &&
!configs.front()->isGoal())
{
Configuration* cfg = configs.front();
configs.pop_front();
//if the configuration cannot lead to the solution, throw away
if(!(cfg->isFailure()))
{
vector<Configuration*> newConfigs = (cfg->getNeighbors());
for(int i = 0; i < newConfigs.size(); i++)
{
Configuration* newConfig = newConfigs[i];
//if it is a new config, not in the map
if(paths.insert(pair<string, Configuration*>(newConfig->toString(), cfg)).second)
{
configs.push_back(newConfig);
}
else
{
//delete newConfig;
}
}
}
}//end while
//if there was a solution, work it out
//if there is none, return empty vector
if(!configs.empty())
{
// put goal configuration value in solutions stack
// find previous configuration in map
// put that configuration value in stack
// continue until previous configuration == current configuration
// in other words, reached initial configuration
//send solution stack back to main
//which will handle printing it out
//remove pointers from previous containers,
//so their deletion doesn't screw things up
Configuration* cfg = configs.front();
configs.pop_front();
string key;
do
{
solutions.push_front(cfg);
key = cfg->toString();
cfg = paths[key];
paths[key] = NULL;
} while(!(cfg->toString() == key));
}
//clean up
for(map<string, Configuration*>::iterator iter = paths.begin();
iter != paths.end();
iter++)
{
delete iter->second; //problem occurs HERE
}
for(int i = 0; i < configs.size(); ++i)
{
delete configs[i];
}
paths.clear();
configs.clear();
return solutions;
}//end solve
Configuration.h
#ifndef CONFIGURATION_H
#define CONFIGURATION_H
#include <string>
#include <vector>
class Configuration
{
public:
/**
* Destructor initialized as virtual so that it can be overridden by
* subclasses.
*/
virtual ~Configuration() {}
/**
* Does this configuration match the target value?
*
* @return true if match, false elsewise
*/
virtual bool isGoal() const;
/**
* Can this configuration ever be solved?
*
* @returns true if impossible to solve, false elsewise
*/
virtual bool isFailure() const = 0;
/**
* Basic string representation of configuration. Differs for each class.
*
* @returns string representation of configuration
*/
virtual std::string toString() const = 0;
/**
* Comparation operator for map-sorting. Compares configuration values.
*
* @returns true if value is greater than other's value, false elsewise
*/
bool operator<(const Configuration& other ) const;
/**
* Return all of this config's neighbors (difference of only a single step).
*
& @returns vector of new configurations
*/
virtual std::vector<Configuration*> getNeighbors() = 0;
/**
*
* @returns string representation of valid steps.
*/
std::string stepsToString();
protected:
// contains the steps that are possible for this configuration to reach
// another valid configuration
static std::vector<int> steps;
//the target configuration value
static int _goal;
//the current value of this configuration
int _value;
};//end Configuration
#endif
Configuration.cpp
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "Configuration.h"
using namespace std;
int Configuration::_goal = 1234;
vector<int> Configuration::steps;
//
// Name: isGoal()
//
bool Configuration::isGoal() const
{
return (_value == Configuration::_goal);
}
//
// Name: operator<
//
bool Configuration::operator<(const Configuration& other) const
{
bool answer = false;
if(_value < other._value) { answer = true; }
return answer;
}
//
// Name: stepsToString
//
string Configuration::stepsToString()
{
stringstream ss;
for(int i = 0; i < Configuration::steps.size(); i++)
{
ss << Configuration::steps[i] << " ";
}
return ss.str();
}
PegConfiguration.cpp
#include <vector>
#include <string>
#include <sstream>
#include "PegConfiguration.h"
using namespace std;
vector<char> PegConfiguration::goal;
/**
* Value constructor.
*
* @param value board value of new Configuration
*/
PegConfiguration::PegConfiguration(vector<char> val, int empty):
value(val),
emptyIndex(empty) {}
/**
* Copy constructor.
*
* @param configuration to copy
*/
PegConfiguration::PegConfiguration(const PegConfiguration::PegConfiguration& copy):
value(copy.value),
emptyIndex(copy.emptyIndex) {}
/**
* Constructor for initial puzzle configuration. Given the initial number
* of pegs on one side of the board, it constructs the initial and goal
* value.
*
* @param numPegs number of pegs on one side of board
*/
PegConfiguration::PegConfiguration(int numPegs):
value(2 * numPegs + 1, '.'),
emptyIndex(numPegs)
{
goal = vector<char>(2 * numPegs + 1, '.');
fill(value.begin(), value.begin() + numPegs, 'R');
fill(value.rbegin(), value.rbegin() + numPegs, 'B');
fill(goal.begin(), goal.begin() + numPegs, 'B');
fill(goal.rbegin(), goal.rbegin() + numPegs, 'R');
}
/**
* Goal configuration is an exact mirror of the initial board.
*
* @returns true if this is the goal configuration.
*/
bool PegConfiguration::isGoal() const
{
return (value == goal);
}
/**
* Is this puzzle impossible to solve? Nope.
*
* @returns false always.
*/
bool PegConfiguration::isFailure() const
{
return false;
}
/**
* Basic string representation of configuration value.
*
* @returns string representation of configuration value
*/
std::string PegConfiguration::toString() const
{
stringstream ss;
for(int i = 0; i < value.size(); ++i)
{
ss << value[i] << " ";
}
return ss.str();
}//end toString
/**
* The empty space can either move one space right or left, or jump
* two spaces right or left, in both cases swapping with the peg in that
* location. The only restriction is where the peg is -- if it's too far
* to the left, it can't jump left, for example.
*
* @returns vector of neighbor configuration pointers
*/
std::vector<Configuration*> PegConfiguration::getNeighbors()
{
vector<Configuration*> neighbors;
//jump one to the left
if((emptyIndex - 1) >= 0)
{
vector<char> newValue(value);
(newValue[emptyIndex], newValue[emptyIndex - 1]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex - 1));
}
//jump two to the left
if((emptyIndex - 2) >= 0)
{
vector<char> newValue(value);
swap(newValue[emptyIndex], newValue[emptyIndex - 2]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex - 2));
}
//jump one to the right
if((emptyIndex + 1) < value.size())
{
vector<char> newValue(value);
swap(newValue[emptyIndex], newValue[emptyIndex + 1]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex + 1));
}
//jump two to the right
if((emptyIndex + 2) < value.size())
{
vector<char> newValue(value);
swap(newValue[emptyIndex], newValue[emptyIndex + 2]);
neighbors.push_back(new PegConfiguration(newValue, emptyIndex + 2));
}
return neighbors;
}//end getNeighbors
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
你的问题很清楚。此行:
在您的求解器函数中,将导致
cfg
指针多次插入paths
映射中。最后,您遍历该地图并删除
其所有元素。如果同一个指针在映射中出现多次,它将被多次删除,因此,您将会崩溃。这也解释了您使用调试器观察到的情况。如果一个类已经被删除,它的 vtable 会“回退”到 vtable 的基本类版本,即正如您所指出的“vanilla”配置。
上面给出的行对我来说看起来很可疑,你确定它是正确的吗?
如果它是正确的,并且
cfg
确实应该在paths
映射中出现多次,那么我建议您使用boost::shared_ptr
或等效的 < code>std::tr1::shared_ptr 来实现您需要的正确引用计数和自动删除。Your problem is clear. This line:
In your solver function will result in the insertion of the
cfg
pointer several times into thepaths
map. At the end, you traverse that map anddelete
all its elements. If the same pointer is present more than once in the map, it will be deleted more than once, and thus, you will get a crash.This also explains what you are observing with the debugger. If a class has already been deleted, its vtable gets "rewinded" to the basic class version of the vtable, i.e. a "vanilla" configuration as you pointed out.
The line given above looks suspicious to me, are you sure it is correct?
If it is correct, and
cfg
should indeed appear several times in thepaths
map, then I suggest you useboost::shared_ptr
or equivalentlystd::tr1::shared_ptr
to implement the proper reference counting and automatic deletion that you need.