【问题标题】:STL: Initializing a container with an unconstructed stateful comparatorSTL:使用未构造的有状态比较器初始化容器
【发布时间】:2013-05-12 20:48:02
【问题描述】:

这一直在我的脑海中作为一个问题的可能解决方案,但是由于它在 C++ 中是对 something 的相当明显的技术违规,我想知道它失败的可能性有多大,是否有另一种相当明显的方法,等等。我希望这不会引发关于未定义行为的激烈争吵,但考虑到这个话题,我确实有点期待。

这不是我正在编写的代码,我希望它不会过于简化以至于无法描述我正在尝试做的事情。

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 中进行任何插入或搜索,这是否是一个问题。

有状态分拣机免责声明

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

【问题讨论】:

  • 我从来没有详细研究过,但是Boost的base_from_member对你有帮助吗?
  • 嗯,好吧?哈哈。该技术可能是一种好方法,它肯定解决了类似(如果不相同)的问题。可能需要进行一些研究才能看到我是否可以使用他们打包的模板,或者我是否被困在我的班级人数翻倍。所以简而言之,该页面中描述的技术是一个潜在的答案。
  • 你的班级人数加倍?嗯?您需要一个额外的类来实现成员习语的基础。把它做成一个模板,它可以被任何类型重用。
  • 除非排序器类本身是一个模板类,在这种情况下就是这样。这意味着将模板类一分为二,一个实现排序器的基类和一个将两者连接在一起的子类。我计划在这里有 4-5 个模板,参数数量不等。

标签: c++ stl construction


【解决方案1】:

std::set&lt;CodePtr,Sorter&gt; 对象存储Sorter 的实例按值 所以当你用Sorter* 构造它时(你的意思是作为引用而不是指针?)它会切片对象,只保留基础部分。

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

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

@Angew 的评论提出了一个好方法,成员习语的基础将允许您确保首先初始化 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(this), SortedBase(SorterFunc(std::ref(m_sorter))) { }

其中m_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;
};

【讨论】:

  • 所以...这听起来几乎压制了在这里使用模板状态比较器的任何尝试,而无需将集合(以及引用它的所有其余代码)拉出基类.还是有一些我没有看到的转折?
  • 好的,多读几遍。看起来确实有可能完成这项工作,我想问题是额外的几个超类和包装类是否值得。
  • 我的版本 确实 工作(请参阅完整示例且没有新类的更新答案)并且您的原始版本甚至无法编译......对我来说似乎值得。跨度>
  • 大声笑,我对我的代码无法编译并不感到惊讶(我确实为这个问题写了新的),尽管我预计比放错位置的 *.我可以确认您的代码不需要拆分模板类或将排序器分解为自己的类;用 m_codeList 的双重构造替换上面提到的 base_from_member 技术似乎是这里最大的区别(如果原始构造需要一个参数,我们可以将其传递给基类中的虚拟 Sorter 对象)。感谢您的额外努力,这似乎是一个权威的答案。
  • 不只是一个放错位置的*,事实上你不能用一个抽象类型实例化std::set来进行比较是最重要的:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-01
  • 1970-01-01
  • 2014-03-22
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多