当容器包含具有指向容器本身的指针的元素时,交换指针交换的标准版本不起作用,并且交换不检查任何此类可能性。 有没有办法告诉容器标准交换不起作用,并使用一些不同的版本? 实现此类交换的最佳方式是什么?
以下示例解释了该问题。
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.
}
答案 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个解决方案:
当然,你也遇到了问题,你将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
中的对象。