STL:用未构造的有状态比较器初始化容器

STL: Initializing a container with an unconstructed stateful comparator

本文关键字:比较器 状态 初始化 STL      更新时间:2023-10-16

这一直在我的脑海中运行,作为一个问题的可能解决方案,然而,由于这是一个相当明显的技术违反的东西在c++中,我想知道它失败的可能性有多大,是否有另一个相当明显的方法,等等。我希望这不会引起关于未定义行为的激烈争论,但考虑到这个主题,我确实希望有一点。

这不是我正在写的代码,我希望它不会太简化而不能描述我想要做的事情。

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;
};

这里的意图

我经常发现,在编写模板类时,拥有一个包含尽可能多的分解逻辑的非模板基类,这对于拥有一些其他代码可以引用的公共类和减少代码重复的数量都很有用,特别是在为同一个类制作五个不同版本,使用不同数量的模板参数时。

在这种情况下,CodePtr是在代码的其他地方生成的(尽管我确实编写了它),并且我希望基于任意数量的任意数据类型查找元素。我一开始考虑了std::multimap,但是key最终会再次成为CodePtr的包装器(或重要块的副本)。

问题

我将有状态排序函数SortedObject5<>::my_sorter传递给SortedBase::m_codeList的构造函数。但是,由于状态排序器位于子类中,因此很明显不是在构造STL集时构造的。

我想知道这是否是一个问题,如果我不做任何插入或搜索m_codeList从任何构造函数。

有状态排序器免责声明

我正式断言(),任何有状态排序函数所使用的规则只有在它控制的STL容器为空或不久之后被清除时才会改变。

std::set<CodePtr,Sorter>对象通过值存储Sorter 的实例,因此当您使用Sorter*构建它时(您是指作为引用而不是指针吗?)它将切片对象并仅保留基部分。

这意味着Sorter复制构造函数将运行并复制未初始化的对象。未定义的行为随之而来。

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

@Angew的评论建议了一个很好的方法,base from成员习惯用法将允许您确保首先初始化m_sorter对象,这是问题的一部分。但这并不能解决切片的问题,要解决这个问题,你需要在排序器周围使用一些包装器,例如

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

然后将包装器传递给set构造函数:

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

如果您从派生类型构造std::function,它将不会被切片。虽然它将被复制,但您可以通过使用引用包装器来防止这种情况:

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

其中m_sorter已经初始化,因为它存储在BaseFromMember基类中,使用base-from-member习惯用法。

:

  1. 首先创建m_sorter ,因此您不会对未初始化的对象做任何操作
  2. 通过引用传递SorterFunc对象
  3. 使用SorterFunc的副本(仍然保持对m_sorter的引用)作为std::set
  4. 的比较函数。

如果你不想使用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(); // 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;
};