C++:抽象指针映射似乎正在切片到抽象类

发布于 2024-10-17 22:53:20 字数 8889 浏览 0 评论 0原文

当我遇到这个问题时,我正在使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

爱情眠于流年 2024-10-24 22:53:20

你的问题很清楚。此行:

if(paths.insert(pair<string, Configuration*>(newConfig->toString(),     cfg)).second)

在您的求解器函数中,将导致 cfg 指针多次插入 paths 映射中。最后,您遍历该地图并删除其所有元素。如果同一个指针在映射中出现多次,它将被多次删除,因此,您将会崩溃。

这也解释了您使用调试器观察到的情况。如果一个类已经被删除,它的 vtable 会“回退”到 vtable 的基本类版本,即正如您所指出的“vanilla”配置。

上面给出的行对我来说看起来很可疑,你确定它是正确的吗?

如果它是正确的,并且 cfg 确实应该在 paths 映射中出现多次,那么我建议您使用 boost::shared_ptr 或等效的 < code>std::tr1::shared_ptr 来实现您需要的正确引用计数和自动删除。

Your problem is clear. This line:

if(paths.insert(pair<string, Configuration*>(newConfig->toString(),     cfg)).second)

In your solver function will result in the insertion of the cfg pointer several times into the paths map. At the end, you traverse that map and delete 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 the paths map, then I suggest you use boost::shared_ptr or equivalently std::tr1::shared_ptr to implement the proper reference counting and automatic deletion that you need.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文