使用自引用元素交换容器

时间:2009-11-17 06:41:29

标签: c++

当容器包含具有指向容器本身的指针的元素时,交换指针交换的标准版本不起作用,并且交换不检查任何此类可能性。 有没有办法告诉容器标准交换不起作用,并使用一些不同的版本? 实现此类交换的最佳方式是什么?

以下示例解释了该问题。

template<template<typename T,typename = std::allocator<T> > class H>
struct kid
{
    typedef H<kid> home_type;
    typedef int id_t;
    kid(const home_type& home,int m,typename home_type::size_type c=-1)
        : home_(&home)
        , me_(m)
        , idx_(c)
    {

    }
    id_t me()const
    {
        return me_;
    }
    id_t cousin()const
    {
        return (*home_).size() < idx_ ? -1 : (*home_)[idx_].me();
    }
private://default copy & assign ok???
    const home_type* home_;
    typename home_type::size_type idx_;
    id_t me_;
};

int main()
{
    typedef kid<std::vector> kid_t;
    typedef std::vector<kid_t> home_t;
    home_t home1,home2;//two neighbors
    home1.push_back(kid_t(home1,1));//elderly kid
    home1.push_back(kid_t(home1,2,0));//the cousins
    home1.push_back(kid_t(home1,3,0));
    home1.push_back(kid_t(home1,4,0));

    home2.push_back(kid_t(home2,10));//first kid
    home2.push_back(kid_t(home2,20));//second kid
    home2.push_back(kid_t(home2,30,0));//cousin of first kid.
    home2.push_back(kid_t(home2,40,1));//cousin of second kid

    for(home_t::const_iterator it = home1.begin(); it!= home1.end(); ++it)
    {
        std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
    }
    std::cout<<"\n";
    for(home_t::const_iterator it = home2.begin(); it!= home2.end(); ++it)
    {
        std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
    }
    std::cout<<"\nexchange home\n";
    std::swap(home1,home2);//they exchanged their house, surely this doesn't work. 
    //what is the best way to do this?
    for(home_t::const_iterator it = home1.begin(); it!= home1.end(); ++it)
    {
        std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
    }
    std::cout<<"\n";
    for(home_t::const_iterator it = home2.begin(); it!= home2.end(); ++it)
    {
        std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
    }
    //all the kids got  confused, they points to neighborhood kids as their cousins 
}

在实际实现中,我有一个带有两个向量v1&amp;的类X. v2,而v1的元素指向v2的一部分。只需交换v1&amp; v2不起作用,因为v1包含指向v2的指针,该指针也需要更新。我正在寻找实现这种交换的最佳方法,而不违反任何封装。

更新

虽然我非常不喜欢孩子的公共set_home,因为这不是孩子api的一部分而且违反了封装,我也意识到std :: vector并不好。需要专门的家庭课程。 始终可以完成基于指针的解决方案,因为每个间接级别都提供了额外的可操作性。但这仅仅改变了问题而不是解决问题。

有一点需要注意的是,孩子们不是独立的物品,他们需要回家,并且这是以孩子的签名编码的。 我觉得,一个nothrow swap只是交换指针是太乐观的看法。容器的交换应该知道它是否可以进行优化。当存储std :: tr1 :: function的std :: vector时,也会发生这种问题,其中functor本身将引用/迭代器存储到向量中。

我有这个解决方案,只是使用友谊选择性地访问 set_home 到家庭类,并存储为值而不是指针。

但我仍然不喜欢这个解决方案,因为我认为交换应该足够智能,以检查它是否可以做它想做的事情,因为它已经有关于 value_type 的信息(这里的孩子实例)它完全不使用。

这是更新的代码。

template<template<typename T,typename = std::allocator<T> > class H>
struct kid
{
  typedef H<kid> home_type;
  typedef int id_t;
  kid(const home_type& home,int m,typename home_type::size_type c=-1)
    : home_(&home)
    , me_(m)
    , idx_(c)
  {

  }
 id_t me()const
 {
    return me_;
 }
 id_t cousin()const
 {
    return (*home_).size() < idx_ ? -1 : (*home_)[idx_].me();
 }
 private:
friend home_type;
void relocate(home_type& home)
{//private, not part of api.only home_type can call it.
    home_ = &home;
}
private://default copy & assign ok???
  const home_type* home_;
  typename home_type::size_type idx_;
  id_t me_;
};

template<typename T,typename Alloc = std::allocator<T> >
class home
{
typedef std::vector<T> base_t;
base_t h_;
public:
typedef typename base_t::size_type size_type;
typedef typename base_t::iterator iterator;
typedef typename base_t::const_iterator const_iterator;
void push_back(const T& t)
{
    h_.push_back(t);
}
size_type size()const
{
    return h_.size();
}
const T& operator[](size_type i)const
{
    return h_[i];
}
iterator begin()
{
    return h_.begin();
}
iterator end()
{
    return h_.end();
}
void swap(home<T,Alloc>& other)//nothrow swap if allocators are equal, though O(N).
{
    using std::swap;
    swap(h_,other.h_);
    for(iterator i = begin(), e = end();i!+ e; ++i)
    {
        (*i).relocate(h_);
    }
    for(iterator i = other.begin(), e = other.end();i!+ e; ++i)
    {
        (*i).relocate(other.h_);
    }
}
};

int main()
{
  typedef kid<home> kid_t;
  typedef home<kid_t> home_t;
  home_t home1,home2;//two neighbors
  home1.push_back(kid_t(home1,1));//elderly kid
  home1.push_back(kid_t(home1,2,0));//the cousins
  home1.push_back(kid_t(home1,3,0));
  home1.push_back(kid_t(home1,4,0));

  home2.push_back(kid_t(home2,10));//first kid
  home2.push_back(kid_t(home2,20));//second kid
  home2.push_back(kid_t(home2,30,0));//cousin of first kid.
  home2.push_back(kid_t(home2,40,1));//cousin of second kid

  for(home_t::const_iterator it = home1.begin(); it!= home1.end(); ++it)
  {
    std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
  }
  std::cout<<"\n";
  for(home_t::const_iterator it = home2.begin(); it!= home2.end(); ++it)
  {
    std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
  }
  std::cout<<"\nexchange home\n";
  using std::swap;
  swap(home1,home2);//they exchanged their house, surely this doesn't work. 
  //what is the best way to do this?
  for(home_t::const_iterator it = home1.begin(); it!= home1.end(); ++it)
  {
    std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
  }
  std::cout<<"\n";
  for(home_t::const_iterator it = home2.begin(); it!= home2.end(); ++it)
  {
    std::cout<<(*it).me()<<" "<<(*it).cousin()<<"\n";
  }
  //everything is fine here.
}

4 个答案:

答案 0 :(得分:2)

你可以为你的类型实现std :: swap:

void std::swap(std::vector<kid_t> &first,std::vector<kid_t> &second)
{
  //here is your implementation or three lines of default swap
}

答案 1 :(得分:1)

我没有看到使用swap执行此操作的任何方法。我能想到的唯一方法是在move中提供kid功能,将一个孩子从一个家搬到另一个家。然后编写自己的函数来移动孩子们。

答案 2 :(得分:0)

这里的问题似乎更多是设计问题而不是交换问题。

你(基本上)使用指向你没有指定自己的变量的指针作为孩子的标识符...当然,一旦孩子们四处移动,它就会破裂。 (实际上它比指针略好,但不是很多)

你有2个解决方案:

  • 基本&gt;自己实例化
  • 更复杂&gt;工厂+ id

当然,你也遇到了问题,你将Home及其实现捆绑在一起......但我会让你自己写一个合适的Home课程。顺便说一句,由于STL容器的模板参数数量取决于容器和您正在使用的STL的实际实现,因此您的解决方案远不如使用真实类的通用;)

<强>基本

class kid
{
public:
  kid(kid* related = 0): m_related(related) {}
  // methods
private:
  // other attributes
  kid* m_related;
};

int main(int argc, char* argv[])
{
  Home home1, home2;
  kid* kid1 = home1.insert(new kid(home1, 1));
  home1.insert(new kid(home1, 2, kid1));

  kid* kid2 = home2.insert(new kid(home2, 1));
  kid* kid3 = home2.insert(new kid(home2, 2));
  home.insert(new kid(home2, 3, kid2));
  home.insert(new kid(home2, 4, kid3));

  swap(home1,home2); // just swapping the vectors, since it only moves pointers not the objects
}

这是简单的方法,但当然我仍然会诅咒你,因为我有2个id为1的孩子......

<强>工厂

 class KidFactory;

 class KidKey { friend class KidFactory; KidKey() {} };

 class Kid
 {
 public:
   Kid(KidKey, Id id, Id cousinId);

   Id getId() const { return m_id; }

   const Home* getHome() const;
   void setHome(const Home& home);

 private:
   const Home* m_home;
   Id m_id;
   Id m_cousinId;
 };

 // Factory
 class KidFactory
 {
 public:
   Kid& createKid(Id cousinId = -1)
   {
     m_kids.push_back(Kid(KidKey(), m_kids.size(), cousinId));
     return m_kids.back();
   }

   Kid& getKid(Id id)
   {
     return m_kids.at(id); // notice that might generate out_of_range exception
   }

 private:
   std::vector<Kid> m_kids;
 };

 class Home
 {
 public:
   void insert(Kid& kid)
   {
     m_kids.push_back(kid.getId());
     kid.setHome(*this);
   }
 };


 int main(int argc, char* argv[])
 {
   KidFactory factory;
   Home home1, home2;

   home1.insert(factory.createKid());
   home1.insert(factory.createKid(home1.front().getId()));

   Kid& kid1 = factory.createKid();
   home2.insert(kid1);
   home2.insert(factory.createKid(kid1.getId()));

   // once again swapping vectors is fine
 }

正确的设计可以轻松编写代码...但是在C ++中有一些样板:(

答案 3 :(得分:0)

我同意问题出在设计中,但我认为说实际上相当简单:你使用对象地址作为对象身份的一种形式。这是一个非常好的事情(这正是为什么C ++禁止0大小的对象 - 所以每个完全派生的对象都有自己独特的地址,因此具有不同的身份),但如果你这样做,你不应该把你的对象视为值类型,并随机复制它们 - 或让任何其他类,如std::vector,这样做。

因此,当涉及到语义时,当你交换代码中的两个向量时,你并没有有效地交换孩子:你在每个房子中杀死孩子,并创建克隆他们在对面的房子里。这可能不是预期的业务逻辑。

我同意Matthieu的观点,如果您使用对象地址作为对象标识,那么您需要自己分配孩子,并在vector中存储指向它们的指针。如果家庭真正拥有孩子,请使用vector<shared_ptr<kid_t>>boost::ptr_vector。如果家里没有孩子,一个孩子可以在没有被怜悯的情况下没有家的情况下生活,那么拥有一个拥有并分配所有孩子的单身list<kid_t>(并且,作为list,将永远不会重新分配隐式地),然后使用vector<kid_t*>指向每个家庭list中的对象。