STL:使用未构造的状态比较器初始化容器 [英] STL: Initializing a container with an unconstructed stateful comparator

查看:147
本文介绍了STL:使用未构造的状态比较器初始化容器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个可能的问题解决方案,但是由于它是一个非常明显的技术违反C ++中的某事,我想知道它是多么可能失败,是否有另一个相当明显的方法等。我希望这不会陷入关于未定义行为的flamewar,但考虑到我期待一点点。



这不是我写的代码,我希望它不是太简单,不能描述我试图做什么。

  class Code 
{
public:
bool read(short slot,short& val);
bool read(short slot,long& val);
bool read(short slot,double& val);
// etc
protected:
unsigned char * m_data;
};
typedef boost :: shared_ptr< Code> CodePtr;

class SortedBase
{
protected:
class Sorter:public std :: binary_function< CodePtr,CodePtr,bool>
{
protected:
inline Sorter(){}
virtual〜Sorter(){}
public:
virtual bool operator ,CodePtr right)PURE;
};

inline SortedBase(Sorter * s):m_codeList(s){}

typedef std :: set< CodePtr,Sorter> TSortedCode;
TSortedCode m_codeList;
public:
virtual〜SortedBase(){}
void fetch(); // populate m_codeList
};

template< class SORT1,class SORT2,class SORT3,class SORT4,class SORT5>
class SortedObject5:public SortedBase
{
public:
SortedObject5():SortedBase(m_sorter),m_sorter(this){}

something_interesting find SORT1 val1,SORT2 val2,SORT3 val3,SORT4 val4,SORT5 val5);
protected:
typedef SortedObject5< SORT1,SORT2,SORT3,SORT4,SORT5>我的课;
class MySorter:public Sorter
{
public:
MySorter(const my_class& parent):m_parent(parent){}
virtual operator CodePtr right);
protected:
const my_class& m_parent;
}

MySorter m_sorter;
};

此处的意图



我经常发现,当编写模板类时,具有尽可能多的因子分解逻辑的非模板基类是有用的,两者都有一些通用类,其他代码可以引用和减少代码重复的数量,特别是在使用不同数量模板参数的同一类的五个不同版本时。



在这种情况下,CodePtr在代码的其他地方生成我想找到基于任意数量的任意数据类型的元素。我首先考虑了一个std :: multimap,但是键最终会成为CodePtr的一个包装器(或一个重要的块的副本)。



问题



我将状态排序函数SortedObject5<> :: my_sorter传递给SortedBase :: m_codeList的构造函数。然而,由于有状态的分拣机在子容器中,显然不是在构建STL集合时构造的。



我想知道如果这是一个问题if我不从任何构造函数在m_codeList中进行任何插入或搜索。



有状态排序器免责声明



我正式的ASSERT(),任何状态排序函数使用的规则只会改变,而它控制的STL容器是空的或将很快之后清除()ed。


< std :: set< CodePtr,Sorter> 对象存储的实例分类器 按值,所以当你用 Sorter * 构建它时(你的意思是,



这意味着 Sorter 复制构造函数将会运行并创建未初始化对象的副本。



假设你甚至可以创建一个 Sorter 的实例,如果它是一个抽象类型你赢了(我不知道你的 PURE 是什么,但我假设你的函数是纯虚拟的)



@ Angew的注释建议一个好的方法,从成员idiom的基础将允许您确保 m_sorter 对象首先被初始化,这是问题的一部分。这不会帮助切片的问题,以解决你需要一些包装器在整理器例如

  typedef std :: function< bool(const CodePtr&,const CodePtr&)> SorterFunc; 
typedef std :: set< CodePtr,SorterFunc> TSortedCode;

然后将包装传递给集合构造函数:

  inline SortedBase(SorterFunc s):m_codeList(s){} 

如果从派生类型构造 std :: function ,它将不会被切片。它会被复制,但你可以通过使用一个引用包装器来防止:

  SortedObject5():BaseFrommember SortedBase(SorterFunc(std :: ref(m_sorter))){} 

> m_sorter 已经初始化,因为它存储在 BaseFromMember 基类中,使用了从成员的基础。



这样:


  1. 创建 m_sorter 第一,因此您不对未初始化的对象执行任何操作

  2. 通过引用传递给 SorterFunc c> c $> $ m_sorter )作为 std :: set
  3. 的比较函数

如果你不想使用base-from-member成语,那么它仍然很容易避免你的原始代码的未定义的行为,只是默认构造 set (而不是传递一个未初始化的对象),然后指定一个新的值,然后开始填充它:

  SortedObject5 ):m_sorter(this)
{
this-> m_codeList = TSortedCode(SorterFunc(boost :: ref(m_sorter)))
}

没有新的基类,没有额外的模板,没有未定义的行为。



这是完整的工作代码:

  class SortedBase 
{
protected:
class Sorter:public std :: binary_function< CodePtr,CodePtr,bool>
{
protected:
Sorter(){}
virtual〜Sorter(){}
public:
virtual bool operator()(const CodePtr& left,const CodePtr& right)= 0;
};

typedef boost :: function< bool(const CodePtr& const,CodePtr&)> SorterFunc;

typedef std :: set< CodePtr,SorterFunc> TSortedCode;

TSortedCode m_codeList;

public:
virtual〜SortedBase(){}
void fetch() // populate m_codeList
};

模板< class SORT1,SORT2类,SORT3类,SORT4类,SORT5类>
class SortedObject5:public SortedBase
{
public:
SortedObject5():m_sorter(* this)
{
this-> m_codeList = TSortedCode SorterFunc(boost :: ref(m_sorter)));
}

protected:
typedef SortedObject5< SORT1,SORT2,SORT3,SORT4,SORT5>我的课;

class MySorter:public Sorter
{
public:
MySorter(const my_class& parent):m_parent(parent){}
virtual bool operator )(const CodePtr& left,const CodePtr& right);
protected:
const my_class& m_parent;
};

MySorter m_sorter;
};


This has been running through my mind as a possible solution to an issue, however as it is a fairly obvious technical violation of something in C++, I wanted to know how likely to it is to fail, whether there is another fairly obvious approach, etc. I'm hoping this doesn't get into a flamewar about undefined behavior, but considering the topic I do expect a little bit.

This is not the code I'm writing, I'm hoping it's not too simplified to not describe what I am attempting to do.

class Code
{
public:
  bool read(short slot, short& val);
  bool read(short slot, long& val);
  bool read(short slot, double& val);
  // etc
protected:
  unsigned char* m_data;
};
typedef boost::shared_ptr<Code> CodePtr;

class SortedBase
{
protected:
   class Sorter : public std::binary_function<CodePtr,CodePtr,bool>
   {
   protected:
     inline Sorter() {}
     virtual ~Sorter() {}
   public:
     virtual bool operator()(CodePtr left, CodePtr right) PURE;
   };

   inline SortedBase(Sorter* s):m_codeList(s) {}

   typedef std::set<CodePtr,Sorter> TSortedCode;
   TSortedCode m_codeList;
public:
   virtual ~SortedBase() {}
   void fetch(); // populates m_codeList
};

template<class SORT1, class SORT2, class SORT3, class SORT4, class SORT5>
class SortedObject5 : public SortedBase
{
public:
  SortedObject5():SortedBase(m_sorter),m_sorter(this) {}

  something_interesting find(SORT1 val1, SORT2 val2, SORT3 val3, SORT4 val4, SORT5 val5);
protected:
  typedef SortedObject5<SORT1,SORT2,SORT3,SORT4,SORT5> my_class;
  class MySorter : public Sorter
  {
  public:
    MySorter(const my_class& parent):m_parent(parent) {}
    virtual operator()(CodePtr left, CodePtr right);
  protected:
    const my_class& m_parent;
  }

  MySorter m_sorter;
};

The intent here

I've often found when writing template classes that having a non-template base class with as much of the factored logic as possible is useful to both have some common class other code can reference and reduce the amount of code duplication, especially when making five different versions of the same class with different numbers of template parameters.

In this case the CodePtr is generated elsewhere in the code (although I did write it) and I would like to find elements based on an arbitrary number of arbitrary datatypes. I considered a std::multimap at first but the key would end up being a wrapper to (or a copy of a significant chunk of) the CodePtr again.

The problem

I am passing the stateful sorter functor SortedObject5<>::my_sorter to the constructor of SortedBase::m_codeList. However because the stateful sorter being in a sublcass, is fairly obviously not constructed at the point that the STL set is constructed.

I'm wondering if this is an issue if I don't make any inserts or searches in m_codeList from either constructor.

Stateful sorter disclaimer

I formally ASSERT() that the rules used by any stateful sort functor will change only while either the STL containers it controls are empty or will be clear()ed shortly afterwards.

解决方案

The std::set<CodePtr,Sorter> object stores an instance of Sorter by value so when you construct it with a Sorter* (did you mean that to be a reference not a pointer?) it will slice the object and only keep the base part.

That means the Sorter copy constructor will run and make a copy of an uninitialized object. Undefined behaviour ensues.

That's assuming you can even create an instance of Sorter, if it's an abstract type you won't be able to (I don't know what your PURE does but I assume you're making the function pure virtual)

@Angew's comment suggest a good approach, the base from member idiom will allow you to ensure the m_sorter object is initialized first, which is part of the problem. That doesn't help the issue of slicing though, to solve that you'd need some wrapper around the sorter e.g.

typedef std::function<bool(const CodePtr&,const CodePtr&)> SorterFunc;
typedef std::set<CodePtr, SorterFunc> TSortedCode;

And then pass the wrapper to the set constructor:

inline SortedBase(SorterFunc s) : m_codeList(s) {}

If you construct the std::function from the derived type it won't be sliced. It will be copied though, but you can prevent that by using a reference wrapper:

  SortedObject5() : BaseFrommember(this), SortedBase(SorterFunc(std::ref(m_sorter))) { }

Where m_sorter is already initialized, because it is stored in the BaseFromMember base class, using the base-from-member idiom.

This:

  1. creates the m_sorter first so you don't do anything with an uninitialized object
  2. passes it by reference to a SorterFunc object
  3. uses a copy of that SorterFunc (still holding a reference to m_sorter) as the comparision function for the std::set

If you don't want to use the base-from-member idiom then it's still easy to avoid the undefined behaviour of your original code, just default construct the set (instead of passing it an uninitialized object) then assign a new value to it before you start populating it:

SortedObject5() : m_sorter(this)
{
  this->m_codeList = TSortedCode(SorterFunc(boost::ref(m_sorter)));
}

No new base classes, no extra templates, no undefined behaviour.

Here's the working code in full:

class SortedBase
{
protected:
   class Sorter : public std::binary_function<CodePtr,CodePtr,bool>
   {
   protected:
     Sorter() {}
     virtual ~Sorter() {}
   public:
     virtual bool operator()(const CodePtr& left, const CodePtr& right) = 0;
   };

   typedef boost::function<bool(const CodePtr&, const CodePtr&)> SorterFunc;

   typedef std::set<CodePtr,SorterFunc> TSortedCode;

   TSortedCode m_codeList;

public:
   virtual ~SortedBase() {}
   void fetch(); // populates m_codeList
};

template<class SORT1, class SORT2, class SORT3, class SORT4, class SORT5>
class SortedObject5 : public SortedBase
{
public:
  SortedObject5() : m_sorter(*this)
  {
    this->m_codeList = TSortedCode(SorterFunc(boost::ref(m_sorter)));
  }

protected:
  typedef SortedObject5<SORT1,SORT2,SORT3,SORT4,SORT5> my_class;

  class MySorter : public Sorter
  {
  public:
    MySorter(const my_class& parent):m_parent(parent) {}
    virtual bool operator()(const CodePtr& left, const CodePtr& right);
  protected:
    const my_class& m_parent;
  };

  MySorter m_sorter;
};

这篇关于STL:使用未构造的状态比较器初始化容器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆