智能指针作为多态的类成员

4
我对智能指针比较陌生,如果有人能给我一些提示,让我确认一下我处理智能指针作为类成员的方式是否正确,我将不胜感激。更具体地说,我想在类多态的上下文中实现这个解决方案,并且最好是异常安全的。
给定一个异构对象容器(std::vector> my_vector),通常的添加元素方法是:my_vector.push_back(shared_ptr(new CChild(1))),这样后来可以通过my_vector[0]->doSomething()调用特定派生类的成员函数。
我想实现的目标是将栈对象添加到向量中,同时仍然能够进行多态。直观地说,类似于:CChild<float> obj1(1); my_vector.push_back(obj1)。为了解决这个问题,我现在使用虚构造函数模式CChild obj1(1); my_vector.push_back(obj1.clone());
请注意,在我的某些派生类中,我有创建对象的静态成员函数,例如:CChild<float> obj1 = CChild<float>::initType2(1); 由于要求问题和为了拥有一个清晰的接口,我现在有一个新类CFoo<T>,它具有作为数据成员的指向CBase<T>类的智能指针。除了包含其他新的私有成员之外,这个类封装/处理派生对象的智能指针,这样我可以做类似这样的事情:
CFoo<float> myfoo(CChild<float>::initType2(1)); my_vector.push_back(myfoo);。这意味着容器现在是类型vector<CFoo<T> >,而不是类型vector<shared_ptr<CBase> >
在这种情况下,我想知道如何实现一个包含智能指针作为类成员的类的构造函数?遵循复制-交换惯用法,如何实现operator =?下面是我设计的一些示例:
template < typename T >
class CBase{
    public:
        CBase(){};
        virtual ~CBase(){};
        ...
        virtual CBase<T> * clone() const = 0;
        virtual CBase<T> * create() const = 0;
};

template < typename T >
class CChild1 : public CBase{
    public:
        ...
        CChild1<T> * clone() const  { return new CChild1<T>(*this); }
        CChild1<T> * create() const { return new CChild1<T>(); }
        static CChild1 initType1(double, double);
        static CChild1 initType2(int);

};

template < typename T >
struct type{
    typedef std::tr1::shared_ptr<T> shared_ptr;
};

template < typename T >
class CFoo{

    public:

        CFoo();
        CFoo( const CBase<T> &, int = 0 );
        CFoo( const CFoo<T> & );
        void setBasePtr( const CBase<T> & );
        void swap( CFoo<T> & );
        CFoo<T> & operator = ( CFoo<T> );
        ...
        ~CFoo();

    private:

        typename type<CBase<T> >::shared_ptr m_ptrBase;
        int m_nParam;

};

template < typename T >
CFoo<T>::CFoo()
    :m_nParam(0)
// How shall I handle here the "m_ptrBase" class member? e.g point it to NULL?
{

}

template < typename T >
CFoo<T>::CFoo(const CBase<T> & refBase, int nParam)
    :m_ptrBase(refBase.clone()), // Is this initialization exception-safe?
    m_nParam(nParam)
{

}

template < typename T >
CFoo<T>::CFoo(const CFoo<T> & refFoo)
    :m_ptrBase(refFoo.m_ptrBase),
    m_nParam(refFoo.m_nParam)
{

}

template < typename T >
void CFoo<T>::setBasePtr( const CBase<T> & refBase ){
    // ??? I would like to do sth. like: m_ptrBase(refBase.clone())
}

template < typename T >
CFoo<T>::~CFoo(){
    // The memory is going to be freed by the smart pointer itself and therefore
    // the destructor is empty, right?
}

template < typename T >
void CFoo<T>::swap( CFoo<T> & refFoo ){
//does this here makes sense?
    using std::swap;

    swap(m_ptrBase, refFoo.m_ptrBase);
    swap(m_nParam, refFoo.m_nParam);

}

template < typename T >
CFoo<T> & CFoo<T>::operator = ( CFoo<T> copyFoo ){
    copyFoo.swap(*this);
    return (*this);
}

以下是我想直观实现的示例。首先,我用包含指向派生类的智能指针以及另一个整型成员的CFoo<float>对象填充容器(请注意,所有这些仅用于说明)。
std::vector<CFoo<float> > my_bank;
for (int b=0; b < 3; b++){
   float x = b*sqrt(2);
   my_bank.push_back( new CFoo<float>( CChild1<float>::initType2(x), b) );
}

for (double s= 1.0; s<= 8.0; s *= 2.0){
    my_bank.push_back( new CFoo<float>( CChild2<float>::initType2(x), 0) );
 }

一旦容器被填满,我想执行一些操作,调用每个派生类中专门定制的虚函数,例如doSomething
for (int i=0; i < (int)my_bank.size(); i++){
    int b = my_bank[i].m_nParam;
    CBase<float>* myChild = my_bank[i].m_ptrBase;

    myChild->doSomething( param1, param2, param3, ..., b);
}

我不确定你的原始问题是否有意义。vector是一个拥有容器,因此您不应该在容器中放置任何自动对象。为什么不先将元素添加到容器中,然后使用该元素代替您原来的自动对象呢?否则,您可以始终插入一份副本:v.push_back(shared_ptr<Base>(new Derived(obj))); - Kerrek SB
@KerrekSB,我不太理解你的第一个建议“先将元素添加到容器中”?你介意说明一下你的建议吗?至于“复制插入”,Derived(obj)是什么意思?我如何添加使用“静态函数成员”创建的对象? - Tin
@KerrekSB,请注意静态函数initType1initType2等静态函数返回的是一个对象而不是指针。所以您建议更改返回类型吗?此外,包装器还包含自己的类成员,而不仅仅是shared_ptr。根据您的建议,最好的解决方案是考虑vector<shared_ptr<CBase<T> >,对吗?我想到的是:v.push_back(new CFoo(CChild::initType2(1), 20))。 - Tin
@KerrekSB,谢谢。我最近学习了关于static函数成员的知识,因此对于这个天真的代码,似乎需要通过地址返回?就我所寻找的内容而言,它与这个之前的帖子有关。我基本上有一系列具有特定特征的图像滤镜。我需要将每个滤镜应用于特定的图像通道。为了处理这个问题,我需要一个滤镜容器(filterbank)。Foo类将是FilterbankElem类,除了滤镜外还有特定的图像通道。 - Tin
@KerrekSB,您说得对。具体的例子很难跟上。我已经更新了我的原始帖子并添加了一些“说明性”的示例,说明我想要实现的内容。如果您能为此提供一些指导,我将不胜感激。 - Tin
显示剩余6条评论
1个回答

3

我真的不知道该如何处理这个问题。你列出的接口要求我一半都不理解,所以请把这个答案当作一个实验性答案,可能与你的问题无关。

我建议你告诉我我的方法缺少什么,我会进行修改。目前我会省略模板,因为它们似乎与问题无关。

因此,毫不拖延,最简单的开始使用智能指针的容器:

#include <vector>
#include <memory>

struct Base
{
  virtual void f();
};

typedef std::shared_ptr<Base> BasePtr;
typedef std::vector<BasePtr> BaseContainer;

struct DerivedA : Base
{
  virtual void f();
  // ...
};

// further derived classes

使用方法:

int main()
{
  BaseContainer v;
  v.push_back(BasePtr(new DerivedB));
  v.push_back(BasePtr(new DerivedC(true, 'a', Blue)));

  BasePtr x(new DerivedA);
  some_func(x);
  x->foo()
  v.push_back(x);

  v.front()->foo();
}

如果您在某个地方有一些自动对象,您可以插入一份副本:
DerivedD d = get_some_d();
v.push_back(BasePtr(new DerivedD(d)));

迭代:

for (BaseContainer::const_iterator it = v.begin(), end = v.end(); it != end; ++it)
{
  (*it)->foo();
}

更新:如果您想在构建后初始化对象,可以尝试以下方法:

{
  DerivedE * p = new DerivedE(x, y, z); 
  p->init(a, b, c);
  v.push_back(BasePtr(p));
}

或者,如果init函数是虚函数,那么更简单:
v.push_back(BasePtr(new DerivedE(x, y, z)));
v.back()->init(a, b, c);
第二次更新:这是一个派生对象可能看起来像什么:
struct DerivedCar : Base
{
  enum EType { None = 0, Porsche, Dodge, Toyota };

  DerivedCar(EType t, bool a, unsigned int p)
  : Base(), type(t), automatic_transmission(a), price(p)
  {
    std::cout << "Congratulations, you know own a " << names[type] << "!\n"; }
  }

private:
  EType type;
  bool automatic_transmission;
  unsigned int price;

  static const std::unordered_map<EType, std::string> names; // fill it in elsewhere
};

用法:Base * b = new DerivedCar(DerivedCar :: Porsche,true,2000);

第三次更新:这只是一个示例,说明如何使用查找表来代替switch语句。假设我们有许多类似的函数(具有相同的签名),我们想根据某个整数使用它们:

struct Foo
{
  void do_a();
  void do_b();
  // ...

  void do(int n)
  {
    switch (n) {
      case 2: do_a(); break;
      case 7: do_b(); break;
    }
  }
};

我们可以将所有函数注册到查找表中,而不是使用开关语句。这里假设支持C++11:

struct Foo
{
  // ...
  static const std::map<int, void(Foo::*)()> do_fns;

  void do(int n)
  {
    auto it = do_fns.find(n);
    if (it != do_fns.end()) { (this->**it)(); }
  }
};

const std::map<nt, void(Foo::*)()> Foo::do_fns {
  { 3, &Foo::do_a },
  { 7, &Foo::do_b },
// ...
};

基本上,您将静态代码转换为容器数据。这总是一件好事。现在它很容易扩展;只需随着新功能的出现添加新函数到查找映射即可。不需要再次触及实际的do()代码!

1
以下是缺失的内容之一:(1)在某些派生类中,我有一些init()函数,它们被用作类似构造函数的东西。我需要它们,因为我有不同的方式来使用相同数量和类型的参数来初始化对象,例如objB.init1(p1,p2); objB.init2(p1,p2); ...而且使用带参数的构造函数,我无法选择init()类型,除非在构造函数上使用switch case,但是我想避免这种情况。这就是第一个问题,考虑到初始化的类型,我该如何做类似于v.push_back(BasePtr(new DerivedC.init3(true, 'a', Blue)));这样的事情。 - Tin
1
首先,将这些“init”函数转换为构造函数是否可行?其次,如果不行,那么对象是否可以存在于已构造但未初始化的状态?(在这种情况下,我的使用示例已经涵盖了如何处理它。) - Kerrek SB
为了解决这个问题,我想可以使用虚拟构造函数技巧来实现:DerivedB objB; objB.init1(p1,p2); v.push_back(BasePtr( objB.clone()); - Tin
1
那并没有解决任何问题:在第一次和第二次调用之间,objB 似乎处于构造但未初始化的状态。没有必要让事情变得复杂。如果这是可以接受的情况,你可以像处理其他情况一样处理它。 - Kerrek SB
关于init函数,我想举个例子。比如说我想创建特定的汽车。例如,Derived b1, b2; b1.initDodge(plateNo, year)b2.initPorsche(plateNo, year)。通过“重构”,您的意思是,最好为每个汽车类型分别创建类?例如,CVehicleCCar: public CVehicle,然后CDodge: public CCarCPorsche: public CCar。对于我的具体情况,我想模拟Kernel1D中给出的init函数的相同功能。 - Tin
显示剩余30条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接