【问题标题】:Inheritance: A template container of base class containing instances of derived classes继承:基类的模板容器,包含派生类的实例
【发布时间】:2012-07-14 08:46:13
【问题描述】:

我按以下方式组织了三个班级。 Foo 是一个模板类,Bar 派生自 FooDoo 也派生自 Foo。它们都实现了doX() 成员函数,该函数在Foo 中定义为虚函数。

我需要一个包含BarDoo 对象的向量(或任何其他容器)。例如,一个包含两个名为vec 的对象的向量,第一个元素应该是Doo,第二个元素应该是Bar。当我调用 vec[0].doX() Doo::doX() 时,应该调用。定义指向Foo 对象的指针向量,完成这项工作。但我不确定实例实际存储在哪里。如果我将指针指向对象,则分配的内存可能会在离开创建对象的范围后被释放。

我准备了一个最小的工作示例来说明问题:

#include <iostream>
#include <vector>

using namespace std;

template <typename T>
class Foo
{
public:
    virtual void doX(){cout<<"doX from Foo"<<endl;}
};

template <typename T>
class Bar : public Foo<T>
{
public:
    void doX(){cout<<"doX from Bar"<<endl;}
    void g(string const &input);    // some test function may be used in doX
    int x;                          // some test data may be used in doX
};

template <typename T>
class Doo : public Foo<T>
{
public:
    void doX(){cout<<"doX from Doo"<<endl;}
};

void doSomething(vector<Foo<int>* >& target)
{
    Foo<int> X;
    // do some extreme job to generate X
    target.push_back(&X); // ==> This is problematic
}

int main()
{
    Foo<int> f;
    Bar<int> b;
    Doo<int> d;
    vector<Foo<int> > v;
    v.push_back(f);
    v.push_back(b);
    v.push_back(d);
    v[0].doX();             // doX from Foo
    v[1].doX();             // doX from Foo
    v[2].doX();             // doX from Foo
    vector<Foo<int>*> v2;
    v2.push_back(&f);       // here is no problem, but if `f` is generated inside
                            // a function that receives a reference to `vec` there
                            // will be problems
    doSomething(v2);        // This is problematic
    v2.push_back(&b);
    v2.push_back(&d);
    v2[0]->doX();           // doX from Foo
    v2[1]->doX();           // doX from Foo but dangerous! May crash
    v2[2]->doX();           // doX from Bar
    v2[3]->doX();           // doX from Doo

    return 0;
}

【问题讨论】:

    标签: c++ oop templates inheritance


    【解决方案1】:

    您已正确识别问题。您必须确保向量中的指针指向的实例至少与向量本身一样长。您可以存储指向动态分配对象的指针,这意味着您负责控制它们的生命周期。这可以通过存储指向使用new 或更好的是smart pointers 创建的实例的指针来实现。

    void addElements(vector<Foo<int>* >& target)
    {
        target.push_back(new Bar<int>());
        target.push_back(new Doo<int>());
    }
    

    在上面的示例中,您必须确保在完成后删除向量的元素(通常在向量超出范围之前)。

    std::unique_ptrs 的 C++11 示例:

    std::vector<std::unique_ptr<Foo<int>> v;
    v.push_back(std::unique_ptr<Foo<int>>(new Bar<int>()); // moves the unique_ptr
    v.push_back(std::unique_ptr<Foo<int>>(new Doo<int>()); // moves the unique_ptr
    v.emplace_back(new Bar<int>()); // constructs the unique_ptr in place
    

    【讨论】:

    • 我以前没有听说过智能指针。上面的代码暂时解决了我的问题。我要去谷歌&lt;memory&gt; 标头。谢谢
    • @sorush-r 对于&lt;memory&gt;,您可能需要 C++11,但 boost 和 TR1 也提供了一些智能指针。
    【解决方案2】:

    我不确定我是否正确理解您的问题,但是执行以下操作有什么问题:

    Foo<int> *x = new ...; // e.g. new Bar<int>()
    target.push_back(x);
    

    这会将 x 的值存储在堆上而不是堆栈上。 x 对象将一直存在,直到您明确删除 if(使用 delete x)。当您不再需要该对象时,您必须调用delete x,否则这部分内存将永远不会被释放,因此您将出现内存泄漏。

    【讨论】:

      【解决方案3】:

      您将实例作为值引用存储在向量中。这将使用隐式复制构造函数从您的 BarDoo 实例构造 Foo 对象。

      您似乎希望将 references 存储到对象中(正如您在示例的后面部分中尝试做的那样)。但是,为了做到这一点,您不能使用 doSomething 函数中的堆栈分配对象,因为一旦函数返回,这些对象就会被释放。

      解决这个问题的一种方法是使用智能指针:

      #include <iostream>
      #include <vector>
      #include <tr1/memory> // assuming g++ now
      
      using namespace std;
      using namespace std::tr1;
      
      template <typename T>
      class Foo
      {
      public:
          virtual ~Foo(){}
          virtual void doX(){cout<<"doX from Foo"<<endl;}
      };
      
      template <typename T>
      class Bar : public Foo<T>
      {
      public:
          void doX(){cout<<"doX from Bar"<<endl;}
          void g(string const &input);    // some test function may be used in doX
          int x;                          // some test data may be used in doX
      };
      
      template <typename T>
      class Doo : public Foo<T>
      {
      public:
          void doX(){cout<<"doX from Doo"<<endl;}
      };
      
      void doSomething(vector<shared_ptr<Foo<int> > >& target)
      {
          Foo<int> X;
          // do some extreme job to generate X
          shared_ptr<Foo<int> > foo(new Foo<int>);
          target.push_back(foo);
      }
      
      int main()
      {
          Foo<int> f;
          Bar<int> b;
          Doo<int> d;
          vector<Foo<int> > v;
          v.push_back(f);
          v.push_back(b);
          v.push_back(d);
          v[0].doX();             // doX from Foo
          v[1].doX();             // doX from Foo
          v[2].doX();             // doX from Foo
          vector<shared_ptr<Foo<int> > > v2;
          v2.push_back(shared_ptr<Foo<int> >(new Foo<int>));
          doSomething(v2);
          v2.push_back(shared_ptr<Foo<int> >(new Bar<int>));
          v2.push_back(shared_ptr<Foo<int> >(new Doo<int>));
          v2[0]->doX();           // doX from Foo
          v2[1]->doX();           // doX from Foo
          v2[2]->doX();           // doX from Bar
          v2[3]->doX();           // doX from Doo
      
          return 0;
      }
      

      【讨论】:

        【解决方案4】:

        既然理论已经解释过了,我就再贴一个代码示例

        #include <iostream>
        #include <vector>
        #include <boost/shared_ptr.hpp>
        
        using namespace std;
        
        template <typename T>
        class Foo
        {
        public:
            virtual void doX(){cout<<"doX from Foo"<<endl;}
        };
        
        
        template <typename T>
        class Bar : public Foo<T>
        {
        public:
            void doX(){cout<<"doX from Bar"<<endl;}
            void g(string const &input);   
            int x;                        
        };
        
        template <typename T>
        class Doo : public Foo<T>
        {
        public:
            void doX(){cout<<"doX from Doo"<<endl;}
        };
        
        typedef boost::shared_ptr<Foo<int> >Ptr;
        
        void doSomething(vector<Ptr>& target)
        {
            target.push_back(Ptr(new Foo<int>));
        }
        
        int main()
        {
            Foo<int> f;
            Bar<int> b;
            Doo<int> d;
        
            vector<Ptr> v;
        
            v.push_back(Ptr(new Foo<int>()));
            doSomething(v2); 
            v.push_back(Ptr(new Bar<int>(b)));
            v.push_back(Ptr(new Doo<int>(d)));
        
            v[0]->doX();         
            v[1]->doX();        
            v[2]->doX();       
            v[3]->doX();      
        
            return 0;
        }
        

        【讨论】:

          【解决方案5】:

          对于多态对象的容器,您有两种选择(现在):

          • 用C++11:典型的stl容器,用unique_ptr:std::vector&lt; std::unique_ptr&lt;T&gt; &gt;
          • 或者简单地说,升压指针容器:boost::ptr_vector&lt;T&gt;

          我个人更喜欢这里的 Boost 指针容器。它们是为此目的而设计的,并提供额外的保证(例如非无效性)和特定的糖衣(取消引用迭代器会产生 T&amp;,而不是需要再次取消引用的 std::unique_ptr&lt;T&gt;&amp;)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-08-24
            • 1970-01-01
            • 2021-04-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-10
            相关资源
            最近更新 更多