2

这一直在我脑海中作为解决问题的可能解决方案,但是由于它是对 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 但关键最终将再次成为 CodePtr 的包装器(或重要块的副本)。

问题

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

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

状态分拣机免责声明

我正式 ASSERT() 任何有状态排序仿函数使用的规则只会在它控制的 STL 容器为空或稍后将被 clear() 时更改。

4

1 回答 1

0

该对象按值std::set<CodePtr,Sorter>存储一个实例,因此当您使用 a 构造它时(您的意思是作为引用而不是指针?)它将对对象进行切片并仅保留基础部分。Sorter Sorter*

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

那是假设您甚至可以创建 的实例Sorter,如果它是您将无法创建的抽象类型(我不知道您PURE做了什么,但我假设您正在使函数成为纯虚拟函数)

@Angew 的评论提出了一个很好的方法,成员习语的基础将允许您确保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))) { }

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

这个:

  1. 创建第m_sorter 一个,因此您不对未初始化的对象执行任何操作
  2. 通过引用一个SorterFunc对象来传递它
  3. 使用它的副本SorterFunc(仍然持有对 的引用m_sorter)作为比较函数std::set

如果您不想使用 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;
};
于 2013-05-17T19:07:01.730 回答