-3

自从我用 C++ 编程以来,已经有3年了。我的多态性不起作用:当我map<string, Base>将我的ArmyBaseNavyBase 对象Base添加到 时,它们将它们转换为对象map,因此GetDescription()返回一个空字符串,而不是我通过ArmyBase::SetDescription()and设置的值NavyBase::SetDescription()。这是极其粗糙的伪代码:

class Base
{ protected:
    string STR__Description;  // Pardon my non-standard style
  public:
    virtual
    string GetDescription()
    { return STR__Description;
    }
    void   SetDescription( string str__Description )
    { STR__Description = str__Description;
    }
}

class ArmyBase: public Base
{ public:
    string GetDescription()
    { return STR__Description + " (Army)";
    }
}

class NavyBase: public Base
{ public:
    string GetDescription()
    { return STR__Description + " (Navy)";
    }
}

听起来map<string, Base*>会导致内存泄漏,我宁愿不升级中期项目以使用shared_ptr. 将派生类实例存储在正确“破坏”它们的容器中是否允许我使用指针map进行多态性而不会有内存泄漏的风险?

Base                         base;
ArmyBase                     base_Army;
set<ArmyBase>                set__ArmyBases;
map<string, Base*>::iterator iter_Bases;
map<string, Base*>           map__Bases;
NavyBase                     base_Navy;
set<NavyBase>                set__NavyBases;
...
while( ... )
{   base_Army = ArmyBase();
    base_Navy = NavyBase();
    ...
    set__ArmyBases.insert(                                        base_Army   );
    map__Bases.insert(     pair<string, Base*>( "Boca Raton",    &base_Army ) );
    ...
    set__NavyBases.insert(                                        base_Navy   );
    map__Bases.insert(     pair<string> Base*>( "NAS Pensacola", &base_Navy ) );
    ...
    base = iter_Bases->second;
    std::cout << ..." " << base->GetDescription() << std::endl;
}

所需的输出map__Bases

Boca Raton ... (Army)
NAS Pensacola ... (Navy)
...
4

5 回答 5

1

您应该使用“new”创建和使用对象的指针,以便在 c++ 中使用多态性。您正在经历的称为对象切片。

于 2013-07-24T08:21:01.560 回答
1

问题是映射条目指向您在堆栈上创建的对象的地址。您将这些对象复制到集合中,但不会将副本的地址存储在地图中。当原始对象超出范围时,它们会被自动删除,因此您的地图条目将变得无效。

我认为解决问题的最佳方法是在堆上分配对象并将指针存储在容器中。当然,这需要您小心内存管理。我知道处理这个问题的三种选择:

  1. 手动内存管理:从容器中删除对象时删除它们。当然,这是危险且容易出错的,但只要小心,您就可以让它发挥作用。我通常通过将容器包装在另一个管理对象的类中来做到这一点,即包装器具有诸如add(Base* base)and之类的方法remove(Base* base),并且它还将在其析构函数中删除容器中的所有对象。当然,您仍然必须注意不要在包装器之外删除此类托管对象。

  2. 智能指针:使用 shared_ptr 或 unique_ptr(取决于所有权语义)并将它们存储在容器中。当对象从容器中移除时,智能指针将负责删除对象。

  3. 使用自定义分配器。您可以使用分配器对 std 容器进行参数化,这应该允许容器在从地图中删除对象时删除它们。但是,我从未这样做过,也无法评论这是否是一个好主意。我听说编写自定义分配器很难做到正确。

我建议使用 1 和 2。我认为这取决于您实际使用的口味和其他要求,但是如果您使用智能指针选项,请确保选择模拟您的特定所有权语义的智能指针(大多数可能是唯一指针)。

于 2013-07-24T08:31:17.173 回答
0

除了代码中有大量可笑的错别字(这就是为什么您应该始终提供SSCCE的原因)之外,您的代码可以在经过两次小的更正后正常工作:

  • 编写一个小的比较函数对象Cmp,通过以下指针比较描述Base
  • 改变你的set<ArmyBase>set<NavyBase>作为set<Base*, Cmp>set<Base*, Cmp>

代码:

#include <string>
#include <set>
#include <map>
#include <iostream>

using namespace std;

class Base
{ protected:
    string STR__Description;  // Pardon my non-standard style
  public:
    virtual
    string GetDescription() const
    { return STR__Description;
    }
    void   SetDescription( string str__Description )
    { STR__Description = str__Description;
    }
};

class ArmyBase: public Base
{ public:
    string GetDescription() const
    { return STR__Description + " (Army)";
    }
};

class NavyBase: public Base
{ public:
    string GetDescription() const
    { return STR__Description + " (Navy)";
    }
};

class Cmp
{
public:
    bool operator()(Base const* lhs, Base const* rhs) const
    { return lhs->GetDescription() < rhs->GetDescription(); };
};

int main()
{

    Base                         base;
    ArmyBase                     base_Army;
    set<Base*, Cmp>           set__ArmyBases;
    map<string, Base*>      map__Bases;
    map<string, Base*>::iterator         iter_Bases;
    NavyBase                     base_Navy;
    set<Base*, Cmp>          set__NavyBases;


    base_Army = ArmyBase(); 
    base_Navy = NavyBase();

    set__ArmyBases.insert(                   &base_Army   );
    map__Bases.insert(     pair<string, Base*>( "Boca Raton",    &base_Army ) );


    set__NavyBases.insert(                   &base_Navy   );
    map__Bases.insert(     pair<string, Base*>( "NAS Pensacola", &base_Navy ) );

    for (auto b: map__Bases)
        std::cout << b.first << "..." << b.second->GetDescription() << std::endl;
}

带输出的实时示例。

没有对象切片或内存错误发生。但是,您正在使用指向作用域对象的原始指针(即,当它们超出作用域时调用它们的析构函数)。

拥有多态对象容器的推荐方法是std::map<std::string, std:unique_ptr<Base>>通过std::make_unique<NavyBase>("NAS Penscalola").

于 2013-07-24T09:07:58.053 回答
0

很少的信息可以真正弄清楚你的问题是什么。但我会从可用的东西中得到一个刺。

你如何检测内存泄漏?ArmyBase如果/包含非 POD 成员,一个明显的问题NavyBase是,如果您从基础中删除它的析构函数将不会被调用,因此您需要一个用于Base.

您建议使用多个不同容器的第二种解决方案在这种情况下几乎肯定是错误的方法。每个派生类型的不同容器首先破坏了多态性的整个目的。此外,您的循环根本没有任何意义。set__ArmyBases/set__NavyBases包含相同的默认构造对象,而map__Bases仅包含指向 2 个全局变量base_Navy&的指针,base_Army每帧都会重新初始化。

于 2013-07-24T08:37:00.087 回答
0

您应该能够使用Boost Intrusive 关联容器获得接近您想要的东西。与标准库容器不同,它们不存储副本而是对对象的引用。这应该允许您的多态性在从侵入式容器中检索对象时起作用。

文档中

侵入式容器和非侵入式容器之间的主要区别在于,在 C++ 中,非侵入式容器存储用户传递的值的副本。...

另一方面,侵入式容器不存储传递对象的副本,而是存储对象本身。...

   acme_intrusive_list<MyClass> list;

   MyClass myclass;
   list.push_back(myclass);

   //"myclass" object is stored in the list
   assert(&myclass == &list.front());

实际上,这意味着您应该使用一个或多个简单的标准库容器(如list<>)来实际存储派生实例,但您将这些实例添加到 Boost.Intrusive 容器中。由于 Boost.Intrusive 容器包含引用,因此如果您销毁实际实例,则 Boost.Intrusive 容器将被损坏。

于 2013-07-24T17:08:26.863 回答